Promise

418 阅读5分钟

前言

JS 引擎是单线程的,同一时刻只能执行一个代码块,所以有一个任务队列来存放即将运行代码。JS引擎中的事件循环程序用于执行队列中下一个任务,负责监控代码执行和管理任务队列。

Promise

当代码中涉及嵌套多个方法调用时,就会使自己陷入回调地狱。 Promise 能解决回调地狱的问题,帮助我们更好跟踪多个回调函数并理清程序操作。
Promise会经历一个生命周期:先是处于进行中(pending)状态,操作完成状态变成已处理(settled)。
已处理状态有两个:

  • Fulfilled,表示 Promise 已完成
  • Rejected,表示 Promise 已拒绝,即 Promise 异步操作失败

所以Promise的状态有3种:"pending"、"fulfilled"、"rejected"。

创建 Promise

创建 Promise 的两种方式:

  • 构造函数
  • Promise 静态方法
let value = 200;

//  构造函数方式
let p1 = new Promise( (resolve, reject) => {
    setTimeout(() => resolve(value), 1000);
});

//静态方法
let p2 = Promise.resolve(value);
let p3 = Promise.reject(new Error ("Explosion!"));

console.log(p1 instanceof Promise);     //  true
console.log(p2 instanceof Promise);     //  true
console.log(p3 instanceof Promise);     //  true

上面示例中创建了3种状态的 Promise:

  1. 构造函数所创建的 Promise 为未解决(pending)状态。
  2. Promise.resolve() 创建的 Promise 为已完成(fulfilled)状态。
  3. Promise.reject() 创建的 Promise 为已拒绝(rejected)状态。

then()

每个 Promise 都有 then() 方法,接受两个参数:第一个是当 Promise 完成时所调用的函数(已完成函数),异步操作完成所附加的数据会传递给这个函数;第二个当 Promise 拒绝时所调用的函数(已拒绝函数),程序失败的错误数据会传递给这个函数。

function fun (value, flag) {
    return new Promise( (resolve, reject) => {
        setTimeout(() => {
            if(flag) {
                resolve(value);
            } else {
                reject(new Error("Explosion!"));
            }
        }, 1000);
    })
}
let p1 = fun("已完成", true);
let p2 = fun("已拒绝", false);

p1.then( value => console.log(value), error => console.log(error.message) );
//  "已完成"
p2.then( value => console.log(value), error => console.log(error.message) );
//  "Explosion!"

catch()

catch()等价于只传入已拒绝处理程序的then()。

let promise = Promise.reject(new Error ("Explosion!"));

promise.catch(error => console.log(error.message));
//  等价于
promise.then(null, error => console.log(error.message));

全局 Promise 拒绝处理

node.js环境

在node.js中,处理 Promise 拒绝时会触发 process 对象上的两个事件:

  • unhandledRejection 在一个事件循环中,当 Promise 已拒绝状态,并且没有相应的拒绝程序处理,则触发该事件。
  • rejectionHandled 在一个事件循环中,当 Promise 已拒绝状态,并且拒绝程序被调用,则触发该事件。
//  unhandledRejection 事件
let reject = Promise.reject(new Error("explosion"));
process.on("unhandledRejection", function (error, promise){
    console.log(reason.message);            //  "explosion"
    console.log(promise === reject);        //  true
});

//  rejectionHandled 事件
process.on("rejectionHandled", function (promise){
    console.log(promise === reject);        //  true
});

//  2秒后调用 catch()
setTimeout(() => reject.catch(() => console.log("拒绝程序调用!") )), 2000;

浏览器环境

浏览器在 window 对象上也有两个触发处理 Promise 拒绝时的事件:

  • unhandledrejection 与 node.js 环境的 unhandledRejection 触发条件一致。
  • rejectionhandled 与 node.js 环境的 rejectionHandled 触发条件一致。
let reject = Promise.reject(new Error ("explosion"));
window.onunhandledrejection = function (event) {
    console.log(event.type);                //  "unhandledrejection"
    console.log(event.promise === reject);  //  true
    console.log(event.reason.message);      //  "explosion"
};
window.onrejectionhandled = function (event) {
    console.log(event.type);                //  "rejectionhandled"
    console.log(event.promise === reject);  //  true
    console.log(event.reason.message);      //  "explosion"
};
//  2秒后调用 catch()
setTimeout(() => reject.catch(() => console.log("拒绝程序调用!") )), 2000;
/*
    如果在创建 reject 之后直接添加拒绝处理程序,
    那么 unhandledrejection 和 rejectionhandled 事件将不被触发,
    因为创建 reject 和添加拒绝程序在同一个事件循环中,
    所以使用setTimeout()方法添加拒绝处理程序。
    同样 node.js 环境下的触发拒绝事件也是。
*/

监听多个 Promise

Promise.all()

Promise.all()方法只接受一个参数并返回一个 Promise,该参数是一个包含多个 Promise 的迭代对象。当所有 Promise 被解决返回的 Promise 才被解决,当所有 Promise 被完成返回的 Promise 才变已完成状态。

let p1 = Promise.resolve(11);
let p2 = Promise.resolve(22);
let promise = Promise.all([p1, p2]);
promise.then( value => {
    console.log(Array.isArray(value));  //  true
    value.forEach(value => console.log(value));
    //  输出: 11,22
});
/*
    返回的 Promise 调用完成处理程序,传入一个数组参数,数组映射多个 Promise 的迭代对象的完成结果。
*/

如果传入Promise.all()的 Promise 中只要有一个被拒绝,那么返回的 Promise 也立即被拒绝,并传入相应错误信息到拒绝处理程序中。

let p1 = Promise.resolve(11);
let p2 = Promise.reject(new Error ("explosion"));
let promise = Promise.all([p1, p2]);
promise.then(null, error => {
    console.log(error.message);     //  "explosion"
});

Promise.race()

Promise.race()也能监听多个 Promise,只接受一个参数,包含多个 Promise 的迭代对象。 当其中一个 Promise 被解决返回的 Promise 才被解决。多个 Promise 会进行竞选,当最先被解决的 Promise 是已完成状态,则返回 Promise 也是已完成状态,并且附加的完成数据传入到完成处理程序中;当最先被解决的 Promise 是已拒绝状态,则返回 Promise 也是已拒绝状态,并且附加的错误信息传入到拒绝处理程序中。

let resolve = Promise.resolve(111);
let reject = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error ("explosion")), 0);
});
let promise = Promise.race([resolve, reject]);
promise.then( value => console.log(value), 
            error => console.log(error.message)
            );  //  111
/*
    resolve 是通过 Promise.resolve() 方法创建,直接创建已完成的 Promise,
    所以 promise 的状态与 resolve 相同。
*/

Promise 派生类

Promise 可以作为基类派生其他类,这样就可以自定义的 Promise 的变量、方法来扩张内建 Promise 的功能。

class MyPromise extends Promise {
    success (resolve, reject) {
        return this.then(resolve, reject);
    }
    failure (reject) {
        return this.catch(reject);
    }
}

let resolve = new MyPromise(resolve => resolve(11));
let reject = MyPromise.reject(new Error("explosion"));
resolve.success(value => console.log(value));          //  11
reject.failure(error => console.log(error.message));  //  "explosion"

扩展 async、await

async 和 await 使执行异步任务的程序语法更加简单。在函数前添加 async 关键字表示该函数以异步方式进行。await 操作符用于等待一个 Promise 对象,当 Promise 被解决,则返回该 Promise 相关附加信息。

function fun (value, flag) {
    return new Promise( (resolve, reject) => {
        setTimeout(() => {
            if(flag) {
                resolve(value);
            } else {
                reject(new Error("Explosion!"));
            }
        }, 1000);
    })
}
async function asyncFun (value, flag) {
    const result = await fun(value, flag);
    console.log(result);                        //  "已完成"
}

let resolve = asyncFun("已完成", true);         
console.log(resolve instanceof Promise);        //  true

如果 await 等待的 Promise 为已完成状态,那么当前代码继续执行并且返回 Promise 的完成值。如果 Promise 为已拒绝状态,那么会抛出错误,当前代码就无法执行,asyncFun() 返回一个隐式已拒绝的 Promise。

async、await 好处就是可以按照同步方式编写异步代码。