关于Promise及其执行顺序总结

Promise简介


Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。

约定

不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:

  • 在本轮事件循环运行完成之前,回调函数是不会被调用的。
  • 即使异步操作已经完成(成功或失败),在这之后通过 then()添加的回调函数也会被调用。
  • 通过多次调用 then() 可以添加多个回调函数,它们会按照插入顺序进行执行。

Promise 很棒的一点就是链式调用(chaining)

Promise原理分析

Promise内部有三个状态:pendingfullfilledrejected
pending是对象创建后的初始状态,当对象fulfill(成功)时变为fulfilled,当对象reject(失败)时变为rejected。且只能从pengding变为fulfilled或rejected ,而不能逆向或从fulfilled变为rejected 、从rejected变为fulfilled。如图所示:
promise状态

Promise实例方法介绍

Promise对象拥有两个实例方法then()catch()

then()方法

成功和失败的回调函数我们是通过then()添加,在promise状态改变时分别调用。promise构造函数中通常都是异步的,所以then方法往往都先于resolve和reject方法执行。所以promise内部需要有一个存储fulfill时调用函数的数组和一个存储reject时调用函数的数组。

then方法可以接收两个参数,且通常都是函数(非函数时如何处理下一篇文章中会详细介绍)。

第一个参数会添加到fulfill时调用的数组中,第二个参数添加到reject时调用的数组中。

当promise状态fulfill时,会把resolve(value)中的value值传给调用的函数中。

var p = new Promise(function(resolve, reject){
    resolve(5)
}).then(function(value){
    console.log(value) //5
})

同理,当promise状态reject时,会把reject(reason)中的reason值传给调用的函数。

var p1 = new Promise(function(resolve, reject){
    reject(new Error('错误'))
}).then(function(value){
    console.log(value)
}, function(reason){
    console.log(reason) //Error: 错误(…)
})

then方法会返回一个新的promise,下面的例子中p == p1将返回false,说明p1是一个全新的对象。

var p = new Promise(function(resolve, reject){
    resolve(5)
})
var p1 = p.then(function(value){
    console.log(value)
})
p == p1 // false

这也是为什么then是可以链式调用的,它是在新的对象上添加成功或失败的回调,这与jQuery中的链式调用不同。

then() 函数会返回一个和原来不同的新的 Promise:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

promise2 不仅表示 doSomething() 函数的完成,也代表了你传入的 successCallback 或者 failureCallback 的完成,这两个函数也可以返回一个 Promise 对象,从而形成另一个异步操作,这样的话,在 promise2 上新增的回调函数会排在这个 Promise 对象的后面。

基本上,每一个 Promise 都代表了链中另一个异步过程的完成。

我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

catch()方法

有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。


Promise时序

为了避免意外,即使是一个已经变成 resolve 状态的 Promise,传递给 then() 的函数也总是会被异步调用:

Promise.resolve().then(() => console.log(2));
console.log(1); 
//输出: 
//1
//2

传递到 then() 中的函数被置入到一个微任务队列中,而不是立即执行,这意味着它是在 JavaScript 事件队列的所有运行时结束了,且事件队列被清空之后,才开始执行:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); 
//输出:
//1
//2
//3
//4

那为什么会输出1 2 3 4 5呢?这就涉及到promise,then,以及setTimeout的执行顺序问题

promise,then,setTimeout的执行顺序

1. setTimeout何时执行?
我们知道,JavaScript是基于事件驱动单线程执行的,所有任务都需要排队,也就是说前一个任务结束,才会去执行下一个任务。而像settimeout、ajax等异步操作的回调,会进入”任务队列“中,而且只有主线程中没有执行任何同步代码的前提下,才会执行异步回调。

2. promise何时执行?
Promise新建后立即执行,也就是说,Promise构造函数里的代码是同步执行的。

3. then何时执行?
then方法指向的回调将在当前脚本所有同步任务执行完后执行

4. 为什么then比setTimeout执行的要早呢?

  • 1) setTimeout的0是否真的为0?

    其实,setTimeout有个最小执行时间(minimum delay of 4ms ),并不是0s执行的。

    注:HTML5中已经将最小执行时间统一为4ms。

  • 2) macrotask 与 microtask

    Macrotasks和Microtasks 都属于异步任务中的一种,常用api分类: macrotasks: setTimeout,
    setInterval, setImmediate, I/O, UI rendering microtasks:
    process.nextTick, Promise, MutationObserver

    一个事件循环中只有一个macrotask任务,可以有一个或多个microtask任务。

再举一个例子:

setTimeout(function() {
   console.log(1)
}, 0);

new Promise(function(resolve, reject) {
  console.log(2)
   for (var i = 0; i < 10000; i++) {
    if(i === 10) {console.log(10)}
       i == 9999 && resolve();
  }
   console.log(3)
}).then(function() {
   console.log(4)
})

console.log(5);

//结果是:2 10 3 5 4 1
  • 首先,setTimeout 被推进到 macrotask 队列(将在下一个macrotask中执行)中。
  • 接着, 会先执行macrotask 中的第一个任务(整个 script中的同步代码 ),再加上promise 构造函数也是同步的(promise.then回调被推进到 microtask 队列中),所以会先打印出2 10 3,然后继续执行末尾的,打印出5
  • 此时,已经执行完了第一个macrotask , 所以接下来会顺序执行所有的 microtask, 也就是 promise.then 的回调函数,从而打印出4。
  • 此时,microtask 队列中的任务已经执行完毕,所以执行剩下的 macrotask 队列中的任务,也就是 setTimeout,所以打印出 1。

参考

MDN Promise
Promise介绍-基础篇
promise,then,setTimeout的执行顺序

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值