ES6 Promise

ES6 Promise

1. 基础

Promise对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值。

用人话来讲,所谓Promise,就是一个用来传递异步操作消息的对象。

《深入理解ES6》中这样描述Promsie:

Promise相当于异步操作结果的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise,承诺将在未来的某个时刻完成。

语法:

new Promise(function(resolve, reject) { } /*executor*/ )

2. 生命周期

graph TD A[unsettled 未处理 - pending 进行中] -->|异步操作| B[settled 已处理] B --> C[Fulfilled 成功] B --> D[Rejected 失败]

内部属性[[PromiseState]]被用来表示Promise的三种状态:pending、fulfilled、rejected。此属性不可访问,而是通过当Promise的状态改变时,使用then()方法来执行后续程序。所有Promise都有then()方法,它接受两个参数:

  • fulfillment function 完成函数:当Promise的状态变为fulfilled时调用
  • rejection function 拒绝函数:当Promise的状态变为rejected时调用

Promise的catch()方法等价于then()方法的第二个参数。

如果一个Promise处于已处理状态,在这之后添加到任务队列中的处理程序仍将执行:

let promise = readFile('example.txt');
promise.then(function(contents) {
    console.log(contents);
    
    promise.then(function(contents) {
        console.log(contents);
    });
});

每次调用then()方法或catch()方法都会创建一个新任务,当Promise被解决(resolved)时执行。

3. 创建未完成的Promise

用Promise构造函数可以创建新的Promise。
构造函数值接受一个参数:executor(resolve, reject)执行器函数。
执行器成功完成时调用resolve()方法,失败时调用reject()

Promise的执行器会立即执行,然后才执行后续流程中的代码:

let promise = new Promise(function(resolve, reject) {
    console.log('Promise');
    resolve();
});
console.log('Hi');
// 结果
// Promise
// Hi

调用resolve()后会触发一个异步操作,传入then()catch()方法的函数会被添加到任务中并异步执行。

let promise = new Promise(function(resolve, reject) {
    console.log('Promise');
    resolve();
});
promise.then(function() {
    console.log('Resolved');
});
console.log('Hi');

// 结果
// Promise
// Hi
// Resolved

4. 创建已处理的Promise

Promise.resolve() 接受一个参数并返回一个完成态的Promise。
Promise.reject() 创建一个已拒绝的Promise。

let promise = Promise.resolve(42);
promise.then(function(value) {
    console.log(value);            // 42
});

如果向Promise.resolve()Promise.reject()传入一个Promise,那么这个Promise会被直接返回。

两者还可以接受非Promise的Thenable对象作为参数。比如:

// 已完成
let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};
let promise = Promise.resolve(thenable);
promise.then(function(value) {
    console.log(value);            // 42
});

// 已拒绝
let thenable1 = {
    then: function(resolve, reject) {       
        reject(24);    
    }};
let promise1 = Promise.resolve(thenable1);
promise1.catch(function(value) {   
    console.log(value); // 24
});

5. 执行器错误

执行器内部错误也会被Promise的拒绝处理程序捕获。

6. 全局的Promise拒绝处理

由于Promise在没有提供拒绝处理程序时,不会提示失败信息或抛出错误。这就需要一些方法来捕获、处理全局的Promise。
*但是我的Chrome(版本 73.0.3683.103(正式版本) (64 位))有未catch的promise会在控制台抛出错误 。如下图所示:

*

6.1 Node.js环境

process对象提供以下两个事件处理Promise拒绝。

  • unhandledRejection 在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时触发
  • rejectionHandled 在一个事件循环后,当Promise被拒绝,且拒绝处理程序被调用时触发

详细参见《深入理解ES6》P248。

6.2 浏览器环境

与Node.js环境不同的就是这两个事件在window对象上:

  • unhandledrejection 在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时,触发该事件
  • rejectionhandled 在一个事件循环后,当Promise被拒绝时,若拒绝处理程序被调用,触发该事件

两个事件处理程序接受一个有以下属性的事件对象作为参数:

  • type 事件名称(unhandledrejection或rejectionhandled)
  • promise 被拒绝的Promise对象
  • reason 来自Promise的拒绝值
let rejected;

window.onunhandledrejection = function(event) {
    console.log(event.type);                           // unhandledrejection
    console.log(event.reason.message);            // Explosion
    console.log(rejected === event.promise);   // true
};

// FIXME 实测这个事件并没有被执行
// 由于当前浏览器端这两个事件兼容性非常差
// 暂时不做细致探究
window.onrejectionhandled = function(event) {
    console.log(event.type);                           // rejectionhandled
    console.log(event.reason.message);            // Explosion
    console.log(rejected === event.promise);   // true
};

rejected = Promise.reject(new Error('Explosion'));

7. 串联Promise

每次调用then()方法或catch()方法时实际上创建并返回了另一个Promise,只有当第一个Promise完成或被拒绝后,第二个才会被解决。

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});

p1.then(function(value) {
    console.log(value);
}).then(function() {
    console.log('Finished');
});

// 结果
// 42
// Finished

7.1 捕获错误

务必在Promise链的末尾留有一个拒绝处理程序以确保能够正确处理所可能发生的错误。

let p2 = new Promise(function (resolve, reject) {
    throw new Error('Explosion');
});

p2.catch(function (reason) {
    console.log(reason.message);
    throw new Error('Boom');
}).catch(function (reason) {
    console.log(reason.message);
});

// 结果
// Explosion
// Boom

7.2 Promise链的返回值

Promise链的另一个重要特性是可以给下游Promise传递数据;如果在完成处理程序中指定一个返回值,则可以沿着这条链继续传递数据。

let p3 = new Promise(function (resolve, reject) {
    resolve(42);
});
p3.then(function (value) {
    console.log(value);
    return value + 1;
}).then(function (value) {
    console.log(value);
});

// 结果
// 42
// 43

拒绝处理程序也可以传递值:

let p4 = Promise.reject(42);

p4.catch(function (reason) {    
    console.log(reason);    
    return reason + 1;
}).then(function (value) {    
    console.log(value);
});

// 结果
// 42
// 43

7.3 Promise链中返回Promise

let p5 = Promise.resolve(42);
let p6 = Promise.resolve(43);

p5.then(function (value) {    
    // 第一个完成处理程序    
    console.log(value);    // 42    
    return p6;
}).then(function (value) {    
    // 第二个完成处理程序    
    console.log(value);    // 43
});

上面是最常见的用法。在完成处理程序中创建新的Promise可以推迟完成处理程序的执行。想要在一个Promise被解决后立即触发另一个Promise,这个模式很有用。

let p7 = Promise.resolve(42);

p7.then(function (value) {    
    console.log(value);   // 42        
    return Promise.resolve(43);
}).then(function (value) {    
    console.log(value);   // 43
});

8. 响应多个Promise

Promise.all()Promise.race()两个方法可以同时监听多个Promise。

8.1 Promise.all()方法

Promise.all()方法只接受一个参数并返回一个Promise
该参数是一个含有多个受监视Promise的可迭代对象,只有当可迭代对象中所有的Promise都被解决后返回的Promsie才会被解决,只有当可迭代对象中所有Promise都被完成后返回的Promise才会被完成。

let p11 = new Promise(function (resolve, reject) {    
    resolve(42);
});
let p12 = new Promise(function (resolve, reject) {    
    resolve(43);
});
let p13 = new Promise(function (resolve, reject) {   
    resolve(44);
});

let p14 = Promise.all([p11, p12, p13]);
p14.then(function (value) {    
    console.log(Array.isArray(value));   // true    
    console.log(value);                     // (3) [42, 43, 44]
});

所有传入Promise.all()的Promise只要有一个被拒绝,那么返回的Promsie不再等所有Promise都完成就立即执行拒绝程序。
拒绝处理程序总是接受一个值而不是数组。这个值来自第一个被拒绝Promise的拒绝值。

8.2 Promise.race()方法

不同于all()方法,只要有一个Promise被解决,返回的Promise就被解决无须等到所有Promise都被完成。

实际上,传给Promise.race()方法的Promsie会进行竞选,以决出哪一个先被解决,如果先解决的是已完成Promise,则返回已完成Promise;如果先解决的是已拒绝Promsie,则返回已拒绝Promise。

let t21 = 1000,   
     t22 = 2000;
let p21 = new Promise(function (resolve, reject) {    
    setTimeout(function () {        
        resolve(42);    
    }, t21);
});
let p22 = new Promise(function (resolve, reject) {    
    setTimeout(function () {        
        reject(43);    
    }, t22);
});

let p23 = Promise.race([p21, p22]);
p23.then(function (value) {    
    console.log(value);
}).catch(function (reason) {    
    console.log(reason);
});

// 结果
// 42

以上例子由于p21先完成,所以执行了then方法。如果我们把t21改成3000,那么最终会执行catch方法,输出43

9. 自Promise继承

Promise与其他内建类型一样,也可以作为基类派生其他类,可以定义自己的Promise变量来扩展内建Promise功能。

参见《深入理解ES6》P262。

10. 基于Promise的异步任务执行

参见《深入理解ES6》P263。 这里不再赘述。

Demo 源码
The end... Last updated by: Jehorn, April 17, 2019, 5:38 PM

原文地址:https://www.cnblogs.com/jehorn/p/10725074.html