javascript_了解JavaScript承诺

javascript

什么是诺言?(What is Promise?)

Promise represents the result of an asynchronous operation. You can think of it as a placeholder for a result that you’ll eventually receive.

承诺表示异步操作的结果。 您可以将其视为占位符,以最终获得结果。

为什么承诺? (Why Promise?)

To solve the problem of callback hell.

解决回调地狱的问题。

Callback hell is any code where the use of function callbacks in asynchronous code becomes difficult to follow. Whenever we have loads of nested callbacks, code becomes harder to read, test, and debug. This also leads to the pyramid of doom.

回调地狱是任何难以在异步代码中使用函数回调的代码。 每当我们有大量的嵌套回调时,代码就变得更难阅读,测试和调试。 这也导致了厄运金字塔。

Here is an example of callback hell.

这是一个回调地狱的例子。

function a() {
  setTimeout(() => {
    setTimeout(() => {
      setTimeout(() => {
        setTimeout(() => {
          setTimeout(() => {
            //do something
          }, 1000);
        }, 1000);
      }, 1000);
    }, 1000);
  }, 1000);
}

承诺的三个状态(Three states of Promise)

Image for post
Three States of Promise
承诺的三个状态

Pending → Before the result is ready, a promise is in a pending state.

待处理→在结果准备就绪之前,承诺处于待处理状态。

Fulfilled → If the result is available it becomes fulfilled

已实现→如果结果可用,则变为已实现

Rejected → If an error occurs, the promise is rejected.

拒绝→如果发生错误,则承诺将被拒绝。

If a promise is not pending i.e it is either fulfilled or rejected, we say it has been settled which means it cannot change its state. That means a promise which has settled remains in that state forever.

如果承诺未挂起也就是说,它要么履行或拒绝,我们就说它已经尘埃落定,这意味着它不能改变其状态。 这意味着已实现的承诺将永远保留在该状态中。

(Example)

const API_URL = 'https://randomuser.me/api/';
const responsePromise = axios.get(API_URL);
console.log(responsePromise); //promise pending


responsePromise.then((response) => {
console.log(response);
});

axios is similar to the fetch API which returns HTTP response as a promise.

axios类似于获取HTTP响应作为承诺的获取API。

On line 3 we see that the promise is pending because promises are asynchronous and therefore take time to settle.

在第3行上,我们看到了Promise正在处理中,因为Promise是异步的,因此需要一些时间来解决。

Image for post

然后() (then())

Promises have a then method that is executed when the promise has been fulfilled. It receives a callback.

承诺有一个then方法,当实现诺言时执行。 它收到一个回调。

When we print the response, we see it is an object. Since the response has a status code of 200 that means our request is successful. It has data property that can be accessed to extract the desired information. In this case the user data.

当我们打印响应时,我们看到它是一个对象。 由于响应的状态码为200,表示我们的请求成功。 它具有可访问以提取所需信息的数据属性。 在这种情况下,用户数据。

Image for post

处理响应 (Manipulating Response)

Usually we want to manipulate the incoming response, so we use Promise Chaining.

通常,我们要处理传入的响应,因此我们使用Promise Chaining。

Chaining: Combining methods one after another where the result of one method depends on another.

链接:一种方法的结果取决于另一种方法,将一种方法又一种方法组合在一起。

Now we will chain then() methods to get the manipulate our response.

现在,我们将链接then()方法来操纵我们的响应。

responsePromise
  .then((response) => {
    return response.data;
  })
  .then((data) => {
    return data.results[0];
  })
  .then((user) => {
    console.log(`Gender : ${user.gender}`);
    console.log(`Email : ${user.email}`);
    console.log(`Phone : ${user.cell}`);
  });

In the above example, the result of the 1st then is passed as a parameter to the 2nd then, similarly the result of 2nd then is passed to or 3rd then and so on.

在上面的示例中,然后将1st的结果作为参数传递给2nd,然后类似地将2nd的结果传递给或3rd然后以此类推。

In production code, we don’t really use a variable for getting a response via axios/fetch. Therefore, from now on we’ll use axios(API_URL).then(…)

在生产代码中,我们实际上并没有使用变量通过axios / fetch获取响应。 因此,从现在开始,我们将使用axios(API_URL).then(...)

抓住() (catch())

Let’s say we have an error (invalid url, non-existent end point, server connection timeout, etc.) while making a request, then we can catch those errors using catch(). Like then(), it also receives a callback.

假设我们在发出请求时遇到错误(网址无效,端点不存在,服务器连接超时等),然后可以使用catch()捕获这些错误。 像then()一样,它也接收回调。

(Example)

Let's say we make a mistake in the URL

假设我们在网址中输入了错误

We just added “-” in the above example API_URL. And named it as incorrect_API.

我们在上面的示例API_URL中仅添加了“-”。 并将其命名为corrective_API。

const incorrect_API = 'https://random-user.me/api/';
axios.get(incorrect_API)
  .then((response) => {
    return response.data;
  })
  .then((data) => {
    console.log(data.results[0].gender);
  })
  .catch((err) => {
    console.warn(err);
    console.log('Failed...');
  });

In the console, we get a network error and our custom error message and a GET error.

在控制台中,我们收到网络错误,自定义错误消息和GET错误。

Image for post

最后() (finally())

The finally() method is used when we want to do something when the promise has settled (i.e. promise is either fulfilled or rejected). It also accepts a callback.

当我们想要在诺言已经解决时(即诺言实现或被拒绝)做某事时,使用finally()方法。 它还接受回调。

Let’s say we want to stop Loading a webpage when the promise has settled.

假设我们希望在诺言达成后停止加载网页。

const API_URL = 'https://randomuser.me/api/';


let isLoading = true;


axios
  .get(API_URL)
  .then((response) => {
    return response.data;
  })
  .then((data) => {
    console.log(data.results[0].gender);
  })
  .catch((err) => {
    console.warn(err);
    console.log('Failed...');
  })
  .finally(() => {
    isLoading = false;
  })
  .then(() => {
    console.log('Inner Loading....' + isLoading);
  });


console.log('Outer Loading....' + isLoading);

In the above example, we see that we can even do more stuff even after we have reached finally. Which in the above case we are just logging the value of isLoading variable.

在上面的示例中,我们看到即使最终达到目标,我们甚至可以做更多的事情。 在上述情况下,我们只是记录isLoading变量的值。

One more thing to notice that our “Outer Loading….true” prints first. Why? Because network requests using promises are asynchronous and therefore non-blocking.

还有一点要注意,我们的“外部加载….true”将首先打印。 为什么呢因为使用Promise的网络请求是异步的,因此是非阻塞的。

The statements outside promise are blocking. To read more blocking/non-blocking statements in depth. Refer to my setTimeout() article here.

promise之外的声明正在阻塞。 阅读更多阻塞/非阻塞语句 深入。 请参阅我的setTimeout()的文章在这里

Image for post

HTTP拦截器 (HTTP Interceptors)

This section has less to do with promises but is a handy axios feature. HTTP interceptors come in handy when you need to examine or change HTTP requests from your application to the server or vice versa (e.g., logging, authentication, etc.)

本节与Promise无关,但它是一个方便的axios功能。 当您需要检查或更改从应用程序到服务器的HTTP请求或反之时(例如,日志记录,身份验证等)时,HTTP拦截器会派上用场

The interceptors run before you make HTTP request via axios.

拦截器在通过axios发出HTTP请求之前运行。

const API_URL = 'https://randomuser.me/api/';
axios.interceptors.request.use((config) => {
  // log a message before any HTTP request is sent
  console.log('Sending Request...');


  return config;
});


axios.get(API_URL).then((response) => {
  return response.data.results[0].gender;
});

resolve()和reject()(resolve() and reject())

We can make a promise that is in the fulfilled state using Promise.resolve()

我们可以使用Promise.resolve()做出处于实现状态的承诺

范例1:(Example 1:)

const resolvedPromise = Promise.resolve(‘I am fulfilled’);console.log(resolvedPromise);
Image for post

One more thing to notice:

需要注意的另一件事:

console.log(Promise.resolve(resolvedPromise) === resolvedPromise); //=>true

范例2: (Example 2:)

We can make a promise that is in rejected state using Promise.reject()

我们可以使用Promise.reject()做出处于拒绝状态的承诺

const rejectedPromise = Promise.reject(new Error(‘I am rejected’));console.log(rejectedPromise);
Image for post

One more thing to notice:

需要注意的另一件事:

console.log(Promise.resolve(rejectedPromise) === rejectedPromise); //true

In the console, you may see an error message because the error is not being caught. We can safely ignore it because it is only for demonstration purposes. In the production code, we handle errors using catch() as we did earlier.

在控制台中,您可能会看到一条错误消息,因为未捕获到该错误。 我们可以放心地忽略它,因为它仅用于演示目的。 在生产代码中,我们像以前一样使用catch()处理错误。

范例3: (Example 3:)

const resolvedPromise2 = Promise.resolve(new Error(‘I am fulfilled’));console.log(resolvedPromise2);
Image for post

Here we see that even if we pass an error into resolve, our promise is still fulfilled.

在这里,我们看到,即使我们将错误传递给解决方案,我们的诺言仍然会兑现。

console.log(Promise.resolve(resolvedPromise2) === resolvedPromise2); //true

转换诺言像对象 (Convert promise like objects)

Promise.resolve() can also convert promise like objects. The below statement converts jQuery that makes Ajax request to a promise.

Promise.resolve()也可以转换类似于对象的promise。 下面的语句将发出Ajax请求的jQuery转换为Promise。

Promise.resolve($.getJSON(API_URL)).then().catch().finally();

创建自定义的承诺 (Creating a custom Promise)

We can in-fact create our won custom promise using the Promise constructor.

我们实际上可以使用Promise构造函数创建获胜的自定义承诺。

The constructor takes in a callback with two parameters: resolve and reject.

构造函数接受带有两个参数的回调:resolve和reject。

We typically write asynchronous operations in the promise and depending on the result, we can either resolve or reject the promise.

我们通常在promise中编写异步操作,根据结果,我们可以解析或拒绝promise。

Here is an example that is automatically fulfilled after 1 second.

这是一个示例,该示例将在1秒钟后自动完成。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});


//Now lets also add then and catch methods to handle the promise


promise
  .then(() => {
    console.log('Fulfilled');
  })
  .catch((err) => {
    console.log('Rejected');
  });

Similarly we can automatically reject our custom promise by replacing resolve() (line 3) with reject().

类似地,我们可以通过将拒绝()(第3行)替换为拒绝()来自动拒绝我们的自定义承诺。

A better approach would be to make a promise reusable by wrapping it inside a function.

更好的方法是通过将诺言包装到函数中来使其可重用。

function sleep(ms) {
  return new Promise((resolve, reject) => setTimeout(resolve), ms);
}
sleep(1000)
  .then(() => console.log('1 second'))
  .then(() => sleep(2000).then(() => console.log('2 second')))
  .catch(() => console.log('In case I am rejected'));

If an error were to occur or we were to throw an error from our custom promise, then our promise would be rejected like following.

如果发生错误或我们从自定义承诺中抛出错误,那么我们的承诺将被拒绝,如下所示。

function sleep(ms) {
  return new Promise((resolve, reject) => {
    throw new Error();
    setTimeout(resolve, ms);
  });
}


sleep(1000)
  .then(() => console.log('Fulfilled'))
  .catch(() => console.log('Rejected'));

We can also handle both resolve and reject something like the following.

我们还可以处理以下问题,也可以拒绝。

function makeHappy(bool) {
  return new Promise((resolve, reject) => {
    if (bool) resolve();
    reject();
  });
}


makeHappy(true)
  .then(() => console.log('I am happy'))
  .catch(() => console.log('I am sad'));

Remember: Whenever resolve is true, the promise executes then() and catch() otherwise

切记:只要resolve为true,则promise就会执行then()和catch(),否则

将回调转换为Promise(Converting callbacks to Promise)

Node.js heavily relies on callbacks and we can easily convert them into promises. But before we do it lets create a file named log.txt which has some text. In my case it is “I am some text”. Let’s try to read a file using node.

Node.js在很大程度上依赖于回调,我们可以轻松地将它们转换为Promise。 但是在执行此操作之前,我们先创建一个名为log.txt的文件,其中包含一些文本。 就我而言,这是“我是一些文字”。 让我们尝试使用node读取文件。

const fs = require('fs');


fs.readFile('log.txt', 'utf8', (error, contents) => {
  if (error) {
    console.log(error);
  } else {
    console.log(contents);
  }
});
Image for post

But if we were to read a file that doesn’t exist, we will get an error.

但是,如果我们要读取不存在的文件,则会收到错误消息。

To convert our callback into a promise we just wrap the promise in a function, add resolve() and reject() and then return it.

要将回调转换为promise,我们只需将promise包装在函数中,添加resolve()和reject(),然后返回它。

const fs = require('fs');
function fileRead(path, encoding) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, encoding, (error, contents) => {
      if (error) {
        reject(error);
      } else {
        resolve(contents);
      }
    });
  });
}


fileRead('log.txt', 'utf8')
  .then((contents) => console.log('Fulfilled : ', contents))
  .catch((error) => console.log('Rejected : ', error));
Image for post

From the above example we can see that we can convert any callback to a promise with the same boilerplate. Therefore, node has a handy utility called promisify that makes the the conversion easy for us.

从上面的示例中,我们可以看到可以使用相同的样板将任何回调转换为promise。 因此,node有一个方便的实用程序叫做promisify ,它使我们的转换变得容易。

const fs = require('fs');
const util = require('util');


const readFile = util.promisify(fs.readFile);


readFile('log.txt', 'utf8')
  .then((contents) => console.log('Fulfilled : ', contents))
  .catch((error) => console.log('Rejected : ', error));
Image for post

Promise.all()(Promise.all())

Let’s go back to our original API_URL example and make two HTTP requests.

让我们回到原始的API_URL示例,并发出两个HTTP请求。

const API_URL = 'https://randomuser.me/api/';


axios
  .get(API_URL)
  .then((response) => response.data.results[0])
  .then((user) => console.log('Gender :', user.gender))
  .then(() => {
    axios
      .get(API_URL)
      .then((response) => response.data.results[0])
      .then((user) => console.log('Gender :', user.gender));
  })
  .catch((err) => console.log('Rejected'));
Image for post

Now if we look in the network tab in developer tools, we see that our request is made one after another i.e. staggered and thus take more time.

现在,如果我们在开发人员工具的“网络”选项卡中查看,我们会看到我们的请求是一个接一个地发出的,即错开了,因此花费了更多时间。

Image for post

Since our requests are not dependent on each other therefore we can use Promise.all().

由于我们的请求彼此不依赖,因此我们可以使用Promise.all()。

Promise.all() takes in multiple promises in an array, executes them simultaneously and returns an array of responses as a promise.

Promise.all()在数组中接受多个promise,同时执行它们,并返回一个响应数组作为promise。

const API_URL_1 = 'https://randomuser.me/api/';
const API_URL_2 = 'https://swapi.dev/api/people/1/';


function getData(url) {
  return axios.get(url);
}


const promise = Promise.all([getData(API_URL_1), getData(API_URL_2)]);


Destructuring the promise variable into response1 and response2
promise.then(([response1, response2]) => {
  const name1 = response1.data.results[0].name;
  console.log('User1 : ', name1.first, name1.last);
  console.log('User2 : ', response2.data.name);
});
Image for post

And when we look at our network tab in developer tools, we will see that both requests were made on parallel thus saving time and boosting performance.

当我们在开发人员工具中查看“网络”标签时,我们会看到两个请求都是并行进行的,从而节省了时间并提高了性能。

Image for post

异步等待 (async and await)

The async keyword before a function means that a function always returns a promise. The keyword await makes JavaScript wait until that promise settles and returns its result. With async and await we can write cleaner asynchronous code.

异步关键字 在函数之前表示函数总是返回promise。 关键字await使JavaScript等待该承诺完成并返回其结果。 使用async和await,我们可以编写更简洁的异步代码。

Let’s see the following example.

让我们看下面的例子。

let isLoading = true;


const API_URL_1 = 'https://randomuser.me/api/';
const API_URL_2 = 'https://swapi.dev/api/people/1/';


function getData(url) {
  return axios.get(url);
}


function main() {
  const promise = Promise.all([getData(API_URL_1), getData(API_URL_2)]);
  const [response1, response2] = [...promise];


  const name1 = response1.data.results[0].name;
  console.log('User1 : ', name1.first, name1.last);
  console.log('User2 : ', response2.data.name);
}


main();
Image for post

The above code throws an error that “promise is not iterable”. Why? Because remember Promise.all() is a async operation i.e. non-blocking, but the code that follows is blocking. So our blocking code runs first. Remember Promise.all() only yields a an array of response promises when all the promises inside are fulfilled. But, in our example the promise is pending and hence our promise is not iterable.

上面的代码抛出一个错误:“ promise is not iterate ”。 为什么呢因为请记住Promise.all()是异步操作,即非阻塞,但是后面的代码是阻塞的。 因此,我们的阻止代码首先运行。 请记住,Promise.all()仅在内部的所有承诺都得到满足时才产生一组响应承诺。 但是,在我们的示例中,承诺尚未实现,因此我们的承诺是不可迭代的。

So to solve this issue we use async-await.

因此,要解决此问题,我们使用async-await。

const API_URL_1 = 'https://randomuser.me/api/';
const API_URL_2 = 'https://swapi.dev/api/people/1/';


function getData(url) {
  return axios.get(url);
}


async function main() {
  const promise = await Promise.all([getData(API_URL_1), getData(API_URL_2)]);
  const [response1, response2] = [...promise];


  const name1 = response1.data.results[0].name;
  console.log('User1 : ', name1.first, name1.last);
  console.log('User2 : ', response2.data.name);
  isLoading = false;
  console.log("Loading : ",isLoading);
}


main();
Image for post

Now we get our desired output. But we still have a problem we need to catch any errors and also maybe print something when the promise is settled.

现在我们得到了期望的输出。 但是我们仍然有一个问题,我们需要捕获任何错误,并且在兑现诺言后也许还要打印一些东西。

One solution is:

一种解决方案是:

let isLoading = true;


const API_URL_1 = 'https://randomuser.me/api/';
const API_URL_2 = 'https://swapi.dev/api/people/1/';


function getData(url) {
  return axios.get(url);
}


async function main() {
  const promise = await Promise.all([getData(API_URL_1), getData(API_URL_2)]);
  const [response1, response2] = [...promise];


  const name1 = response1.data.results[0].name;
  console.log('User1 : ', name1.first, name1.last);
  console.log('User2 : ', response2.data.name);
  isLoading = false;
  console.log('Loading : ', isLoading);
}


main().catch((err) => {
  console.log('Error Found...');
  console.log(err);
  isLoading = false;
  console.log('Loading : ', isLoading);
});

In the above code we see that we are duplicating code to change isLoading and also printing it. A better solution would be to use a try-catch statement and handle isLoading with finally() like the following.

在上面的代码中,我们看到我们正在复制代码以更改isLoading 并打印出来。 更好的解决方案是使用try-catch语句并使用finally()处理isLoading,如下所示。

let isLoading = true;


const API_URL_1 = 'https://randomuser.me/api/';
const API_URL_2 = 'https://swapi.dev/api/people/1/';


function getData(url) {
  return axios.get(url);
}


async function main() {
  try {
    const promise = await Promise.all([getData(API_URL_1), getData(API_URL_2)]);
    const [response1, response2] = [...promise];
    const name1 = response1.data.results[0].name;


    console.log('User1 : ', name1.first, name1.last);
    console.log('User2 : ', response2.data.name);
  } catch (err) {
    console.log('Error Found...');
    console.log(err);
  } finally {
    isLoading = false;
    console.log('Loading : ', isLoading);
  }
}


main();

翻译自: https://levelup.gitconnected.com/understanding-javascript-promises-5040a634c474

javascript

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值