JavaScript Promise教程–如何在JS中解决或拒绝承诺

2020年12月30日10:52:01 发表评论 35 次浏览

本文概述

诺言s是JavaScript中异步操作的重要构建块。你可能认为诺言不是那么容易理解, 学习和使用。相信我, 你并不孤单!

对于许多Web开发人员而言, 即使在与他们合作多年之后, 其承诺仍具有挑战性。

在本文中, 我想尝试改变这种看法, 同时分享过去几年我对JavaScript Promises的了解。希望你觉得它有用。

一种诺言是一个特殊的JavaScript对象。它产生一个值异步(aka, 异步)操作成功完成, 如果由于超时, 网络错误等原因未能成功完成操作, 则会发生错误。

呼叫成功完成由指示解决函数调用, 并且错误由拒绝函数调用。

你可以使用promise构造器创建promise, 如下所示:

let promise = new Promise(function(resolve, reject) {    
    // Make an asynchronous call and either resolve or reject
});

在大多数情况下, promise可用于异步操作。但是, 从技术上讲, 你可以解析/拒绝同步和异步操作。

哦, 是的!那就对了。我们有打回来JavaScript中的函数。但是, 回调在JavaScript中并不是什么特别的事情。这是一个常规函数, 会在异步呼叫完成(成功/错误)。

"异步"一词意味着将来会发生某些事情, 而不是现在。通常, 仅在进行网络调用或上载/下载内容, 与数据库对话等操作时才使用回调。

而回叫很有帮助, 它们也有很大的缺点。有时, 我们可能在另一个回调中包含一个回调, 而在另一个回调中, 依此类推。我是认真的!让我们用一个例子来理解这个"回调地狱"。

如何避免回调地狱– PizzaHub示例

让我们从PizzaHub订购Veg Margherita披萨🍕。当我们下订单时, PizzaHub自动检测我们的位置, 找到附近的披萨餐厅, 并查找我们要的披萨是否可用。

如果有的话, 它会检测我们随披萨一起免费获得的饮料, 最后下订单。

如果成功下订单, 我们将收到一条带有确认的消息。

那么我们如何使用回调函数对此进行编码?我想到了这样的事情:

function orderPizza(type, name) {
    
    // Query the pizzahub for a store
    query(`/api/pizzahub/`, function(result, error){
       if (!error) {
           let shopId = result.shopId;
           
           // Get the store and query pizzas
           query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
               if (!error) {
                   let pizzas = result.pizzas;
                   
                   // Find if my pizza is availavle
                   let myPizza = pizzas.find((pizza) => {
                       return (pizza.type===type && pizza.name===name);
                   });
                   
                   // Check for the free beverages
                   query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
                       if (!error) {
                           let beverage = result.id;
                           
                           // Prepare an order
                           query(`/api/order`, {'type': type, 'name': name, 'beverage': beverage}, function(result, error){
                              if (!error) {
                                  console.log(`Your order of ${type} ${name} with ${beverage} has been placed`);
                              } else {
                                  console.log(`Bad luck, No Pizza for you today!`);
                              }
                           });

                       }
                   })
               }
           });
       } 
    });
}

// Call the orderPizza method
orderPizza('veg', 'margherita');

让我们仔细看看orderPizza以上代码中的功能。

它调用一个API来获取你附近的比萨店的ID。之后, 它将获得该餐厅中可用披萨的列表。它检查是否找到了我们要的披萨, 并进行了另一个API调用以查找该披萨的饮料。最后, 订单API下订单。

在这里, 我们为每个API调用使用一个回调。这导致我们在前一个内部使用另一个回调, 依此类推。

这意味着我们进入了我们称之为(非常表达)的事物回调地狱。谁想要那个?它还形成了一个代码金字塔, 不仅令人困惑, 而且容易出错。

JavaScript Promise教程–如何在JS中解决或拒绝承诺1

回调地狱和金字塔的演示

有几种方法可以出来(或不进入)回调地狱。最常见的一种是使用诺言or异步的功能。但是, 要了解异步的运作良好, 你需要对诺言首先。

因此, 让我们开始吧, 潜入诺言。

只是为了回顾一下, 可以使用构造函数语法创建一个promise, 如下所示:

let promise = new Promise(function(resolve, reject) {
  // Code to execute
});

构造函数将一个函数作为参数。此功能称为执行器功能.

// Executor function passed to the 
// Promise constructor as an argument
function(resolve, reject) {
    // Your logic goes here...
}

executor函数有两个参数, 解决和拒绝。这些是JavaScript语言提供的回调。你的逻辑位于执行程序函数中, 该执行程序在以下情况下自动运行:新的承诺被建造。

为了使承诺有效, 执行程序函数应调用任一回调函数, 解决or拒绝。我们将在一段时间内详细了解更多。

的新的Promise()构造函数返回一个诺言目的。由于执行程序功能需要处理异步操作, 因此返回的promise对象应该能够通知执行开始, 完成(已解决)或错误重新调整(已拒绝)的时间。

一种诺言对象具有以下内部属性:

  1. 州–此属性可以具有以下值:
  • 待定:最初是在执行者函数开始执行时。
  • 完成:兑现诺言时。
  • 拒绝:当承诺被拒绝时。
JavaScript Promise教程–如何在JS中解决或拒绝承诺2

承诺状态

2.结果–此属性可以具有以下值:

  • 未定义:最初当州值是待定.
  • 值: 什么时候解析(值)叫做。
  • 错误: 什么时候拒绝(错误)叫做。

这些内部属性不可访问代码, 但可以检查。这意味着我们将能够检查州和结果属性值使用调试器工具, 但是我们将无法使用该程序直接访问它们。

JavaScript Promise教程–如何在JS中解决或拒绝承诺3

能够检查承诺的内部属性

一个承诺的状态可以是待定, 完成or拒绝。被解决或被拒绝的承诺被称为定居.

JavaScript Promise教程–如何在JS中解决或拒绝承诺4

兑现的诺言被兑现或被拒绝

如何兑现诺言

这是一个可以解决的承诺的示例(完成状态)与值我做完立即。

let promise = new Promise(function(resolve, reject) {
    resolve("I am done");
});

下面的承诺将被拒绝(拒绝状态)与错误消息有点不对劲!.

let promise = new Promise(function(resolve, reject) {
    reject(new Error('Something is not right!'));
});

需要注意的重要一点:

无极执行者仅应召集一个解决方案或一个拒绝方案。一旦一个状态被更改(待处理=>已实现或待处理=>被拒绝), 仅此而已。进一步的解决或拒绝呼叫将被忽略。

let promise = new Promise(function(resolve, reject) {
  resolve("I am surely going to get resolved!");

  reject(new Error('Will this be ignored?')); // ignored
  resolve("Ignored?"); // ignored
});

在上面的示例中, 仅第一个要解析的将被调用, 其余的将被忽略。

一种诺言使用执行程序功能来完成任务(通常是异步完成)。当执行程序函数通过解决(成功)或拒绝(错误)完成时, 应该通知使用者函数(使用promise的结果)。

处理程序方法, 。然后(), 。抓住()和。最后(), 有助于在执行程序和使用者功能之间创建链接, 以便在许诺时它们可以同步解决s或拒绝s.

JavaScript Promise教程–如何在JS中解决或拒绝承诺5

执行者和消费者功能

如何使用。然后()承诺处理者

的。然后()应该在promise对象上调用方法来处理结果(解决)或错误(拒绝)。

它接受两个函数作为参数。通常, 。然后()应该从使用者函数中调用方法, 在该函数中你想知道承诺执行的结果。

promise.then(
  (result) => { 
     console.log(result);
  }, (error) => { 
     console.log(error);
  }
);

如果你仅对成功的结果感兴趣, 则可以将一个参数传递给它, 如下所示:

promise.then(
  (result) => { 
      console.log(result);
  }
);

如果你只对错误结果感兴趣, 则可以通过null对于第一个参数, 像这样:

promise.then(
  null, (error) => { 
      console.log(error)
  }
);

但是, 你可以使用。抓住()我们将在一分钟内看到的方法。

我们来看几个使用结果处理结果和错误的示例。然后和。抓住处理程序。通过一些实际的异步请求, 我们将使学习变得更加有趣。我们将使用PokeAPI获取有关神奇​​宝贝的信息并使用Promises解决/拒绝它们。

首先, 让我们创建一个通用函数, 该函数接受PokeAPI URL作为参数并返回Promise。如果API调用成功, 则返回已解决的Promise。任何类型的错误均会返回被拒绝的承诺。

从现在开始, 我们将在几个示例中使用此功能以取得承诺并继续工作。

function getPromise(URL) {
  let promise = new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest();
    req.open("GET", URL);
    req.onload = function () {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject("There is an Error!");
      }
    };
    req.send();
  });
  return promise;
}

承诺的实用方法

示例1:获取50个神奇宝贝的信息:

const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50';

// We have discussed this function already!
let promise = getPromise(ALL_POKEMONS_URL);

const consumer = () => {
    promise.then(
        (result) => {
            console.log({result}); // Log the result of 50 Pokemons
        }, (error) => {
            // As the URL is a valid one, this will not be called.
            console.log('We have encountered an Error!'); // Log an error
    });
}

consumer();

示例2:让我们尝试一个无效的URL

const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/';

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.then(
        (result) => {
            // The promise didn't resolve. Hence, it will
            // not be executed.
            console.log({result});
        }, (error) => {
            // A rejected prmise will execute this
            console.log('We have encountered an Error!'); // Log an error
        }
    );
}

consumer();

如何使用。抓住()承诺处理者

你可以使用此处理程序方法来处理来自承诺的错误(拒绝)。传递的语法null作为第一个参数。然后()不是处理错误的好方法。所以我们有。抓住()用一些简洁的语法完成相同的工作:

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.catch(error => console.log(error));
}

consumer();

如果我们抛出一个错误新的错误("出问题了!")而不是打电话给拒绝从诺言执行者和处理者那里, 它仍将被视为拒绝。这意味着这将被。抓住处理程序方法。

这对任何人都是一样的同步Promise执行程序和处理程序函数中发生的异常。

这是一个将其视为拒收和。抓住处理程序方法将被调用:

new Promise((resolve, reject) => {
  throw new Error("Something is wrong!");// No reject call
}).catch((error) => console.log(error));

如何使用。最后()承诺处理者

的。最后()处理程序执行清除操作, 例如停止加载程序, 关闭活动连接等。的最后()不管一个诺言是否被调用解决s或拒绝s。它会将结果或错误传递给下一个可以再次调用.then()或.catch()的处理程序。

这是一个示例, 可以帮助你一起理解所有三种方法:

let loading = true;
loading && console.log('Loading...');

// Gatting Promise
promise = getPromise(ALL_POKEMONS_URL);

promise.finally(() => {
    loading = false;
    console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
    console.log({result});
}).catch((error) => {
    console.log(error)
});

进一步说明:

  • 的。最后()方法使加载false.
  • 如果诺言解决了, 。然后()方法将被调用。如果承诺因错误而拒绝, 则。抓住()方法将被调用。的。最后()无论解决还是拒绝, 都将被调用。

的promise.then()呼叫总是返回一个承诺。这个承诺将有州as待定和结果as未定义。它允许我们调用下一个。然后新诺言的方法。

当第一次。然后方法返回一个值, 下一个。然后方法可以接收到。现在第二个可以传递到第三个。然后()等等。这形成了一个链。然后兑现承诺的方法。这种现象称为承诺链.

JavaScript Promise教程–如何在JS中解决或拒绝承诺6

承诺链

这是一个例子:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
}).catch(error => {
    console.log('In the catch', error);
});

在这里, 我们首先解决了一个Promise, 然后提取URL到达了第一个神奇宝贝。然后, 我们返回该值, 并将其作为承诺传递给下一个.then()处理函数。因此输出

https://pokeapi.co/api/v2/pokemon/1/

的。然后方法可以返回:

  • 值(我们已经看到了)
  • 一个崭新的承诺。

它还可能引发错误。

这是一个示例, 其中我们使用。然后返回结果和新承诺的方法:

// Promise Chain with multiple then and catch
let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
}).then(pokemon => {
    console.log(JSON.parse(pokemon));
}).catch(error => {
    console.log('In the catch', error);
});

在第一。然后调用我们提取URL并将其作为值返回。该网址将传递给第二个。然后调用我们将以该URL作为参数返回新承诺的地方。

这个承诺将得到解决, 并传递到我们获得有关神奇宝贝的信息的链条中。这是输出:

JavaScript Promise教程–如何在JS中解决或拒绝承诺7

承诺链调用的输出

如果出现错误或承诺被拒绝, 则将调用链中的.catch方法。

注意事项:。然后多次不会形成一个Promise链。你可能最终会做这样的事情, 只是在代码中引入了一个错误:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
});
promise.then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
});
promise.then(pokemon => {
    console.log(JSON.parse(pokemon));
});

我们称。然后方法对相同的承诺进行三遍, 但我们不会将承诺传递给其他人。这不同于承诺链。在上面的示例中, 输出将是错误。

JavaScript Promise教程–如何在JS中解决或拒绝承诺8

除了处理程序方法(.then, .catch和.finally)之外, Promise API中还有六个可用的静态方法。前四个方法接受一个诺言数组并并行运行它们。

  1. 无极
  2. 承诺
  3. 承诺全部解决
  4. 无极种族
  5. 承诺解决
  6. 承诺拒绝

让我们逐一进行。

Promise.all()方法

Promise.all([promises])接受promise的集合(例如, 数组)作为参数, 并并行执行它们。

此方法等待所有promise解析并返回promise结果数组。如果任何承诺由于错误而拒绝或执行失败, 则所有其他承诺结果将被忽略。

让我们创建三个诺言, 以获取有关三个神奇宝贝的信息。

const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur';
const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate';
const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna';


let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
let promise_2 = getPromise(RATICATE_POKEMONS_URL);
let promise_3 = getPromise(KAKUNA_POKEMONS_URL);

通过传递一个Promise数组来使用Promise.all()方法。

Promise.all([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('An Error Occured');
});

输出如下:

JavaScript Promise教程–如何在JS中解决或拒绝承诺9

如你在输出中看到的, 将返回所有promise的结果。执行所有承诺的时间等于承诺运行所需的最长时间。

Promise.any()方法

Promise.any([promises])-类似于所有()方法, 。任何()也接受一系列的诺言, 以并行执行它们。这种方法不会等待所有诺言解决。当任何一项诺言得以兑现时, 便完成了。

Promise.any([promise_1, promise_2, promise_3]).then(result => {
     console.log(JSON.parse(result));
 }).catch(error => {
     console.log('An Error Occured');
 });

输出将是任何已解决的承诺的结果:

JavaScript Promise教程–如何在JS中解决或拒绝承诺10

Promise.allSettled()方法

romise.allSettled([承诺])-此方法等待所有promise结算(解决/拒绝)并将其结果作为对象数组返回。如果满足, 结果将包含状态(已实现/已拒绝)和值。如果状态被拒绝, 它将返回错误原因。

这是所有已兑现承诺的示例:

Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('There is an Error!');
});

输出如下:

JavaScript Promise教程–如何在JS中解决或拒绝承诺11

如果任何诺言都拒绝了, 例如诺言_1,

let promise_1 = getPromise(POKEMONS_BAD_URL);
JavaScript Promise教程–如何在JS中解决或拒绝承诺12

Promise.race()方法

Promise.race([promises])–它等待第一个(最快速的)承诺达成, 并相应地返回结果/错误。

Promise.race([promise_1, promise_2, promise_3]).then(result => {
    console.log(JSON.parse(result));
}).catch(error => {
    console.log('An Error Occured');
});

输出已解决的最快承诺:

JavaScript Promise教程–如何在JS中解决或拒绝承诺13

Promise.resolve / reject方法

Promise.resolve(值)–它通过传递给它的值来解决一个承诺。与以下内容相同:

let promise = new Promise(resolve => resolve(value));

Promise.reject(错误)–它拒绝已传递错误的承诺。与以下内容相同:

let promise = new Promise((resolve, reject) => reject(error));

当然, 让我们做吧。让我们假设询问方法将返回一个承诺。这是一个示例query()方法。在现实生活中, 此方法可能会与数据库对话并返回结果。在这种情况下, 它非常硬编码, 但起着相同的作用。

function query(endpoint) {
  if (endpoint === `/api/pizzahub/`) {
    return new Promise((resolve, reject) => {
      resolve({'shopId': '123'});
    })
  } else if (endpoint.indexOf('/api/pizzahub/pizza/') >=0) {
    return new Promise((resolve, reject) => {
      resolve({pizzas: [{'type': 'veg', 'name': 'margherita', 'id': '123'}]});
    })
  } else if (endpoint.indexOf('/api/pizzahub/beverages') >=0) {
    return new Promise((resolve, reject) => {
      resolve({id: '10', 'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  } else if (endpoint === `/api/order`) {
    return new Promise((resolve, reject) => {
      resolve({'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  }
}

接下来是我们的重构回调地狱。为此, 首先, 我们将创建一些逻辑函数:

// Returns a shop id
let getShopId = result => result.shopId;

// Returns a promise with pizza list for a shop
let getPizzaList = shopId => {
  const url = `/api/pizzahub/pizza/${shopId}`;
  return query(url);
}

// Returns a promise with pizza that matches the customer request
let getMyPizza = (result, type, name) => {
  let pizzas = result.pizzas;
  let myPizza = pizzas.find((pizza) => {
    return (pizza.type===type && pizza.name===name);
  });
  const url = `/api/pizzahub/beverages/${myPizza.id}`;
  return query(url);
}

// Returns a promise after Placing the order
let performOrder = result => {
  let beverage = result.id;
   return query(`/api/order`, {'type': result.type, 'name': result.name, 'beverage': result.beverage});
}

// Confirm the order
let confirmOrder = result => {
    console.log(`Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!`);
}

使用这些功能可以创建所需的承诺。这是你应该与回调地狱例。这是如此的优雅。

function orderPizza(type, name) {
  query(`/api/pizzahub/`)
  .then(result => getShopId(result))
  .then(shopId => getPizzaList(shopId))
  .then(result => getMyPizza(result, type, name))
  .then(result => performOrder(result))
  .then(result => confirmOrder(result))
  .catch(function(error){
    console.log(`Bad luck, No Pizza for you today!`);
  })
}

最后, 通过传递披萨的类型和名称来调用orderPizza()方法, 如下所示:

orderPizza('veg', 'margherita');

如果你在这里并已阅读以上大部分内容, 那么恭喜!你现在应该更好地掌握JavaScript Promises。本文中使用的所有示例均在此GitHub资料库.

接下来, 你应该了解异步的JavaScript中的函数, 进一步简化了事情。 JavaScript承诺的概念最好通过编写小示例并在其之上构建来学习。

无论我们使用什么框架或库(Angular, React, Vue等), 异步操作都是不可避免的。这意味着我们必须理解使事情变得更好的承诺。

另外, 我确定你会发现取现在, 方法要容易得多:

fetch('/api/user.json')
.then(function(response) {
    return response.json();
})
.then(function(json) {
    console.log(json); // {"name": "tapas", "blog": "notlogy"}
});
  • 的取方法返回一个承诺。所以我们可以称之为。然后处理程序方法就可以了。
  • 剩下的就是我们在本文中学到的承诺链。

感谢你阅读本文!让我们连接。你可以@我Twitter(@tapasadhikary)有评论。

你可能还会喜欢以下其他文章:

  • JavaScript未定义且为null:我们最后一次谈谈它!
  • JavaScript:使用==, ===和Object.is进行相等比较
  • JS初学者介绍了JavaScript`this`关键字+ 5个键绑定规则
  • JavaScript TypeOf –如何在JS中检查变量或对象的类型

目前为止就这样了。我的下一篇文章很快再见。在此之前, 请保重身体。

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: