图片来源https://objcer.com/2017/05/07/About-Promise/
Promise是前端目前不论面试还是工作中都应该十分了解的知识,本文希望全面并且简单的能够把promise的概念、使用、原理讲清楚,所以分了为了三篇。
这第一篇使用基本就是一些我自己的概念理解,以及许多部分搬运阮一峰大大的文章,将一部分最核心的篇幅,重新学习,并且记录了一下。
一、什么是promise 文档解读:
1 2 3 Promises/A+ 规范中表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符。 严谨来讲,Promise 是一种封装和组合未来值得易于复用机制,实现关注点分离、异步流程控制、异常冒泡、串行/并行控制等。
白话解读:
Promise是异步编程 的一种解决方案
Promise,简单说就是一个容器 ,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
从语法上说,Promise 是一个对象 ,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
二、Promise使用 1. 基本用法 (1) 创造了一个Promise实例 1 2 3 4 5 6 7 8 9 const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
(2) 指定回调函数 1 2 3 4 5 promise.then(function(value) { // success }, function(error) { // failure });
then方法可以接受两个回调函数作为参数。
[1]第一个回调函数是Promise对象的状态变为resolved时调用,
[2]第二个回调函数是Promise对象的状态变为rejected时调用。
[3]其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
[4]Promise 新建后就会立即执行 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let promise = new Promise (function (resolve, reject ) { console .log('Promise' ); resolve(); }); promise.then(function ( ) { console .log('resolved.' ); }); console .log('Hi!' );
[5]调用resolve或reject并不会终结 Promise 的参数函数的执行。
1 2 3 4 5 6 7 8 new Promise ((resolve, reject ) => { resolve(1 ); console .log(2 ); }).then(r => { console .log(r); });
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
1 2 3 4 5 new Promise ((resolve, reject ) => { return resolve(1 ); console .log(2 ); })
(3) promise嵌套 果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
1 2 3 4 5 6 7 8 const p1 = new Promise (function (resolve, reject ) { }); const p2 = new Promise (function (resolve, reject ) { resolve(p1); })
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
2. api讲解 (1)Promise.prototype.then() 它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
1 2 3 4 5 getJSON("/posts.json" ).then(function (json ) { return json.post; }).then(function (post ) { });
(2)Promise.prototype.catch() Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
1 2 3 4 5 6 getJSON('/posts.json' ).then(function (posts ) { }).catch(function (error ) { console .log('发生错误!' , error); });
如果 Promise 状态已经变成resolved,再抛出错误是无效的。
1 2 3 4 5 6 7 8 const promise = new Promise (function (resolve, reject ) { resolve('ok' ); throw new Error ('test' ); }); promise .then(function (value ) { console .log(value) }) .catch(function (error ) { console .log(error) });
(3)Promise.prototype.finally() finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
1 2 3 4 promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 promise .finally(() => { }); promise .then( result => { return result; }, error => { throw error; } );
实现:
1 2 3 4 5 6 7 Promise .prototype.finally = function (callback ) { let P = this .constructor; return this .then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
从上面的实现还可以看到,finally方法总是会返回原来的值。
1 2 3 4 5 6 7 8 9 10 11 // resolve 的值是 undefined Promise.resolve(2).then(() => {}, () => {}) // resolve 的值是 2 Promise.resolve(2).finally(() => {}) // reject 的值是 undefined Promise.reject(3).then(() => {}, () => {}) // reject 的值是 3 Promise.reject(3).finally(() => {})
(4)Promise.all() Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1 const p = Promise .all([p1, p2, p3]);
p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法 ,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p的状态由p1、p2、p3决定,分成两种情况。
[1]只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
[2]只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
(5)Promise.race() Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
1 const p = Promise .race([p1, p2, p3]);
(6)Promise.resolve(); Promise.resolve方法的参数分成四种情况。
[1]参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
[2]参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
1 2 3 4 5 let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
1 2 3 4 5 6 7 8 9 10 let thenable = { then: function (resolve, reject ) { resolve(42 ); } }; let p1 = Promise .resolve(thenable);p1.then(function (value ) { console .log(value); });
[3]参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
1 2 3 4 5 6 const p = Promise .resolve('Hello' );p.then(function (s ) { console .log(s) });
上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。
[4]不带有任何参数
1 2 3 4 5 const p = Promise.resolve(); p.then(function () { // ... });
上面代码的变量p就是一个 Promise 对象。
需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
1 2 3 4 5 6 7 8 9 10 11 12 13 setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。
(7)Promise.reject() Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
1 2 3 4 5 6 7 8 const p = Promise .reject('出错了' );const p = new Promise ((resolve, reject ) => reject('出错了' ))p.then(null , function (s ) { console .log(s) });
上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
(8)Promise.try() [1] 无法或不想区分,数f是同步函数还是异步操作
1 Promise .resolve().then(f)
[2] 让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API
(1) async函数来写
1 2 3 4 5 const f = () => console .log('now' );(async () => f())(); console .log('next' );
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
1 2 (async () => f())() .then(...)
需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。
1 2 3 (async () => f())() .then(...) .catch(...)
(2) 第二种写法是使用new Promise()
1 2 3 4 5 6 7 8 9 const f = () => console.log('now'); ( () => new Promise( resolve => resolve(f()) ) )(); console.log('next'); // now // next
上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
(3) Promise.try
事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
1 2 3 4 5 6 7 try { database.users.get({id : userId}) .then(...) .catch(...) } catch (e) { }
上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。
1 2 3 Promise .try(database.users.get({id : userId})) .then(...) .catch(...)
事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。