2018/2/2 21:30:31当前位置推荐好文程序员浏览文章

一、概述
1、什么是 Promise?
MDN 对 Promise 的定义:

The Promise object represents the eventual completion (or failure) of an asynchronous operation, its resulting value.
直译:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。

通俗的说,这个异步操作可以一起执行多个任务,函数调用后不会立即返回执行的结果。如果任务A需要等待,可先执行任务B,等到任务A结果返回后再继续回调。
如下,定时器的异步模式:

// code1setTimeout(function() {    console.log(taskA, 定时器异步);}, 0);console.log(taskB, 同步操作);// taskB, 同步操作// taskA, 定时器异步

由 code1 可知,异步任务总是在当前脚本执行完同步任务后才执行任务
2、为什么要用 Promise?
在我们写JavaScript 时,难免是要用到回调函数的,有时候需要多层嵌套回调,有时极端时,会出现厄运回调(如图-厄运回调金字塔):

厄运回调地狱
一般我们在项目开发中用的比较多的情况是在使用 ajax 时:

// code 2request(test1.html, , function(data1) {    console.log(第一次请求成功, 这是返回的数据:, data1);    request(test2.html, data1, function (data2) {        console.log(第二次请求成功, 这是返回的数据:, data2);        request(test3.html, data2, function (data3) {            console.log(第三次请求成功, 这是返回的数据:, data3);            //request... 继续请求        }, function(error3) {            console.log(第三次请求失败, 这是失败信息:, error3);        });    }, function(error2) {        console.log(第二次请求失败, 这是失败信息:, error2);    });}, function(error1) {    console.log(第一次请求失败, 这是失败信息:, error1);});

上述 code2 代码出现了多层嵌套现象,这样并不利于编码维护和编程体验。
这时,我们就可以借用 promise 的强大作用,用 then 链式回调把上述 code2 代码简化如下 code3:

// code3sendRequest(test1.html, ).then(function(data1) {    console.log(第一次请求成功, 这是返回的数据:, data1);}).then(function(data2) {    console.log(第二次请求成功, 这是返回的数据:, data2);}).then(function(data3) {    console.log(第三次请求成功, 这是返回的数据:, data3);}).catch(function(error) {    //用catch捕捉前面的错误    console.log(sorry, 请求失败了, 这是失败信息:, error);});

上面代码code3 是不是看起来清爽啦!这就是 Promise 的强大之处。
二、基本方法
1、基本用法
Promise 有以下三种状态:

  • Pending:进行中,表示初始值
  • Fulfilled:表示操作成功
  • Rejected:表示操作失败

Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就 凝固 了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then绑定的函数就会被调用。
2、基本API
.then()

语法:Promise.prototype.then(onFulfilled, onRejected)

对 promise 添加 onFulfilledonRejected 回调,并返回的是一个新的Promise实例(不是原来那个Promise实例),且返回值将作为参数传入这个新Promise的 resolve 函数。

因此,我们可以使用链式写法,如上文的 code3。由于前一个回调函数,返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用
.catch()

语法:Promise.prototype.catch(onRejected)

该方法用于指定放生错误时的回调函数。

// code4promise.then(function(data) {    console.log(success);}).catch(function(error) {    console.log(error, error);});

Promise 对象捕获错误,还有其它的等同写法:

// code5var promise = new Promise(function (resolve, reject) {    throw new Error(test);});/等同于/var promise = new Promise(function (resolve, reject) {    reject(new Error(test));});//用catch捕获promise.catch(function (error) {    console.log(error);});// Error: test

从 code5 中可以看出,reject 方法的作用,等同于抛错。
Promise 对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个 catch 所捕获。then 方法指定的回调函数,若抛出错误,也会被下一个 catch 捕获,catch 中也能抛错,则需要后面的 catch 来捕获。

// code6sendRequest(test.html).then(function(data1) {    //do something}).then(function (data2) {    //do something}).catch(function (error) {    //处理前面三个Promise产生的错误});

Promise 状态一旦改变就会凝固,不会在改变。一次 Promise 一旦 fulfilled 了,再抛错,也不会变为 rejected,就不会被 catch 了。

// code7var promise = new Promise(function(resolve, reject) {  resolve();  throw error;});promise.catch(function(e) {   console.log(e);      //This is never called});//在回调函数前抛异常var p1 = {     then: function(resolve) {      throw new Error("error");      resolve("Resolved");    }};var p2 = Promise.resolve(p1);p2.then(function(value) {    //not called}, function(error) {    console.log(error);       // => Error: error});//在回调函数后抛异常var p3 = {     then: function(resolve) {        resolve("Resolved");        throw new Error("error");    }};var p4 = Promise.resolve(p3);p4.then(function(value) {    console.log(value);     // => Resolved}, function(error) {    //not called});

如果没有使用 catch 方法指定处理错误的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应(chrome 会抛错,Safari 和 Firefox 不抛错),这是 Promise 的一个缺点。

// code8var promise = new Promise(function (resolve, reject) {    resolve(x);});promise.then(function (data) {    console.log(data);});// Uncaught (in promise) ReferenceError: x is not defined

.all()

语法:Promise.all(iterable)

该方法把多个 Promise 实例,打包成一个新的 Promise 实例

// code9var p = Promiese.all([p1, p2, p3]);

Promise.all 方法接受一个数组(或具有 Iterator 接口的对象)作为参数,数组中的对象([p1, p2, p3)均为 Promise 实例(如果不是一个 Promise,该项会被用 Promise.resolve 放转换为一个 Promise),它的状态由这个上 Promise 实例决定。

  • 当三个实例的状态都变味 fulfilled,p的状态菜户变为 fulfilled,并将三个 Promise 返回的结果,按照参数的顺序(而不是 resolved 的顺序)存入数组,传给 p 的回调函数,如 code10。
  • 当三个实例中有一个装填为 rejected,p 的状态也会变为 rejected,并把第一个 reject 的 Promise 的返回值,传给 p 的回调函数,如 code11.
// code10var p1 = new Promise(function (resolve, reject) {    setTimeout(resolve, 3000, "first");});var p2 = new Promise(function (resolve, reject) {    resolve(second);});var p3 = new Promise((resolve, reject) => {  setTimeout(resolve, 1000, "third");}); Promise.all([p1, p2, p3]).then(function(values) {   console.log(values); });// 约 3s 后["first", "second", "third"] 
// code11var p1 = new Promise((resolve, reject) => {   setTimeout(resolve, 1000, "one"); }); var p2 = new Promise((resolve, reject) => {   setTimeout(reject, 2000, "two"); });var p3 = new Promise((resolve, reject) => {  reject("three");});Promise.all([p1, p2, p3]).then(function (value) {    console.log(resolve, value);}, function (error) {    console.log(reject, error);    // => reject three});// reject three

这多个 promise 是同时开始、并行执行的,而不是顺序执行。从下面 code12 例子可以看出。如果一个个执行,那至少需要 1+32+64+128。

// code12function timerPromisefy(delay) {    return new Promise(function (resolve) {        setTimeout(function () {            resolve(delay);        }, delay);    });}var startDate = Date.now();Promise.all([    timerPromisefy(1),    timerPromisefy(32),    timerPromisefy(64),    timerPromisefy(128)]).then(function (values) {    console.log(Date.now() - startDate + ms);    console.log(values);});// 130ms       //不一定,但大于128ms[1,32,64,128]

.race()

语法: Promise.race(iterable)

该方法也是将多个 Promise 实例,打包成一个新的 Promise 实例。

var p = Promise.race([p1, p2, p3]);

Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中任一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的 promise 的返回值,传给 p 的回调函数。

// code13var p1 = new Promise(function(resolve, reject) {     setTimeout(reject, 500, "one"); });var p2 = new Promise(function(resolve, reject) {     setTimeout(resolve, 100, "two"); });Promise.race([p1, p2]).then(function(value) {    console.log(resolve, value); }, function(error) {    //not called    console.log(reject, error); });// resolve twovar p3 = new Promise(function(resolve, reject) {     setTimeout(resolve, 500, "three");});var p4 = new Promise(function(resolve, reject) {     setTimeout(reject, 100, "four"); });Promise.race([p3, p4]).then(function(value) {    //not called    console.log(resolve, value);              }, function(error) {    console.log(reject, error); });// reject four

.resolve()

语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);

该方法可以看做 new Promise() 的快捷方式。

// code14Promise.resolve(Success);/等同于/new Promise(function (resolve) {    resolve(Success);});

这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。

// code15Promise.resolve(success).then(function (value) {    console.log(value);});// Success
// code16//Resolving an arrayPromise.resolve([1,2,3]).then(function(value) {  console.log(value[0]);    // => 1});//Resolving a Promisevar p1 = Promise.resolve(this is p1);var p2 = Promise.resolve(p1);p2.then(function (value) {    console.log(value);     // => this is p1});

Promise.resolve()的另一个作用就是将thenable对象(即带有then方法的对象)转换为promise对象。

// code17var p1 = Promise.resolve({     then: function (resolve, reject) {         resolve("this is an thenable object!");    }});console.log(p1 instanceof Promise);     // => truep1.then(function(value) {    console.log(value);     // => this is an thenable object!  }, function(e) {    //not called});

.reject()

语法: Promise.reject(reason)

这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。

// code 18Promise.reject(new Error(error));/等同于/new Promise(function (resolve, reject) {   reject(new Error(error));});

3、两个附加方法

.done()

语法:
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();

Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误

.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

// code19Promise.resolve().then(()=>{if(needToContinueProcess)   return xxx;return Promise.reject({final})}).then(processOne).then(processTwo).catch(err=>{if(err instanceof Error) return handleError}).finally()`

三、使用注意事项
缺点:
1、无法取消 Promise。一旦新建它就会立即执行,无法中途取消。
2、如果不设置回调函数,Promise 内部抛出的错误不会反应到外部。
3、当处于 pending 状态时,无法得知目前进展到哪一阶段。
注意事项:
1、始终在Promise 构造器中书写逻辑的话,即使出现了意外的输入,也能绝大部分情况下返回一个Rejected 的 Promise
2、在 Promise 构造器中,除非你明确知道使用 throw 的正确姿势,否则都请使用 reject。
3、能够兼容 Promise 和 Callback 确实是件很棒的事情,用第三方代码库(如bluebird)前请尽量理解其原理,短小的话完全可以自己写一个。Promise虽好,可不要乱用哦,实时牢记它会吞没错误的风险。
四、自我检验题
https://zhuanlan.zhihu.com/p/30797777
五、结语
本章的重点是需要明白什么是Promise?为什么要使用 Promise?在使用 Promise 知道基本用法和注意事项。下一章我们来认识一下 ES6 中的遍历器 Iterator。
六、参考

  • https://segmentfault.com/a/1190000007032448#articleHeader6
  • 阮一峰《ES5标准入门(第三版)》
网友评论