promise简介

一、 什么是Promise

在《你不知道的JavaScript中》有个场景介绍得很形象:

    我走到快餐店的柜台,点了一个芝士汉堡。我交给收银员1.47美元。通过下订单并付款,我已经发出了一个对某个值(就是那个汉堡)的请求。我已经启 动了一次交易。
    但是,通常我不能马上就得到这个汉堡。收银员会交给我某个东西来代替汉堡:一张带有订单号的收据。订单号就是一个 IOU(I owe you, 我欠你的)承诺(promise),保证了最 终我会得到我的汉堡。  
    在等待的过程中,我可以做点其他的事情。事实上,订单号当作芝士汉堡的占位符了。从本质上讲,这个占位符使得这个值不再依赖时间。这是一个未来值。
    终于, 我听到服务员在喊“订单 113” , 然后愉快地拿着收据走到柜台, 把收据交给收银 员,换来了我的芝士汉堡。
    换句话说, 一旦我需要的值准备好了, 我就用我的承诺值(value-promise)换取这个值 本身。
    但是,还可能有另一种结果。他们叫到了我的订单号,但当我过去拿芝士汉堡的时候,收银员满是歉意地告诉我:“不好意思,芝士汉堡卖完了。”除了作为顾客对这种情况感到愤怒之外,我们还可以看到未来值的一个重要特性:它可能成功,也可能失败。

    每次点芝士汉堡,我都知道最终要么得到一个芝士汉堡,要么得到一个汉堡包售罄的坏消息,那我就得找点别的当午饭了。

  所以Promise的出现其实是作为异步编程的一种解决方案。比传统的解决方案-回调函数和事件-更加合理、强大。Promise相当于异步操作结果的占位符,他不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise相当于订单号)。

二、 为什么使用Promise

当我们采用回调模式实现异步编程时,容易产生回调地狱,即在一个回调函数中嵌套多个回调函数,使得代码难以理解和调试,例如:

method1(function(err,result) {
    if (err) {
        throw err;
    }
    method2(function(err, result) {
        if (err) {
            throw err;
        }
        method3(function(err, result) {
            if (err) {
                throw err;
            }
            method4(function(err, result) {
                if (err) {
                    throw err;
                }
                method5(result);
            });
        });
    });
});

  所以,如果你想实现更复杂的功能,回调函数的局限性就显现出来。例如,并行在执行两个异步操作,当操作都结束时通知你;或者同时进行两个异步操作,只取优先完成的操作结果。而Promise就能很好的实现这些情况。下面我们就来介绍一下Promise的基本用法。

三、 Promise的基本用法

1、promise的生命周期

    每个 Promise都会经历一个短暂的生命周期,初始为挂起态( pending state),这表示异步操作尚未结束。一个挂起的 Promise 也被认为是未决的( unsettled )。一旦异步操作结束, Promise就会被认为是已决的( settled ),并进入两种可能状态之一:
        1. 已完成(fulfilled ): Promise 的异步操作已成功结束;
        2. 已拒绝(rejected ): Promise 的异步操作未成功结束,可能是一个错误,或由其他原因导致。

    一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then绑定的函数就会被调用。注意:Promise一旦新建就会「立即执行」,无法取消。这也是它的缺点之一。

2、创建未决的 Promise

    新的 Promise使用 Promise 构造器来创建。此构造器接受单个参数:一个被称为执行器(executor)的函数,包含初始化Promise 的代码。该执行器会被传递两个名为 resolve()与 reject() 的函数作为参数。 resolve() 函数在执行器成功结束时被调用,用于示意该Promise 已经准备好被决议( resolved ),而 reject() 函数则表明执行器的操作已失败。

//构建Promise
var promise = newPromise(function (resolve, reject) {
    if (/* 异步操作成功 */) {
        resolve(data);
    } else {
        /* 异步操作失败 */
        reject(error);
    }
});

    要记住执行器会在Promise被创建时立即运行。当 resolve() 或 reject() 在执行器内部被调用时,一个作业被添加到作业队列中,以便决议(resolve )这个 Promise 。这被称为作业调度(job scheduling ),若你曾用过 setTimeout() 或 setInterval() 函数,那么应该已经熟悉这种方式。在作业调度中,你添加新作业到队列中是表示:“不要立刻执行这个作业,但要在稍后执行它”。Promise 工作方式与 setTimeout() 相似,此处有个例子:

let promise = newPromise(function(resolve, reject) {
console.log("Promise");
resolve();
});
promise.then(function(){
console.log("Resolved.");
});
console.log("Hi!");

此例的输出结果为:

Promise
Hi!
Resolved

    Promise的执行器会立即执行,早于源代码中在其之后的任何代码。调用 resolve()触发了一个异步操作。传递给 then() 与 catch() 的函数会异步地被执行,并且它们也被添加到了作业队列(先进队列再执行),将在当前脚本所有同步任务执行完才会执行。

3、then方法和catch方法

    then()方法在所有的 Promise上都存在,并且接受两个参数。第一个参数是 Promise 被完成时要调用的函数,与异步操作关联的任何附加数据都会被传入这个完成函数。第二个参数则是 Promise 被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的任何附加数据。

    用这种方式实现 then()方法的任何对象都被称为一个 thenable 。所有的 Promise 都是thenable ,反之则未必成立。

    传递给 then()的两个参数都是可选的,因此你可以监听完成与拒绝的任意组合形式。例如,研究这组 then() 调用:

let promise =readFile("example.txt");
promise.then(function(contents){
// 完成
console.log(contents);
}, function(err) {
// 拒绝
console.error(err.message);
});
promise.then(function(contents){
// 完成
console.log(contents);
});
promise.then(null,function(err) {
// 拒绝
console.error(err.message);
});

    这三个 then()调用都操作在同一个 Promise 上。第一个调用同时监听了完成与失败;第二个调用只监听了完成,错误不会被报告;第三个则只监听了拒绝,并不报告成功信息。

    Promis也具有一个 catch()方法,其行为等同于只传递拒绝处理函数给 then() 。例如,以下的 catch() 与 then() 调用是功能等效的。

promise.catch(function(err){
    //拒绝
    console.error(err.message);
});
// 等同于:
promise.then(null,function(err) {
    //拒绝
    console.error(err.message);
});

Promise的状态一经改变,就固定不变。所以,即使完成或拒绝处理函数在 Promise已经被解决之后才添加到作业队列,它们仍然会被执行。这允许你随时添加新的完成或拒绝处理函数,并保证它们会被调用。例如:

let promise =readFile("example.txt");
// 原始的完成处理函数
promise.then(function(contents){
    console.log(contents);
    //现在添加另一个
    promise.then(function(contents){
        console.log(contents);
    });
});

在此代码中,完成处理函数又为同一个 Promise添加了另一个完成处理函数。这个 Promise此刻已经完成了,因此新的处理程序就被添加到任务队列,并在就绪时(前面的作业执行完毕后)被调用。拒绝处理函数使用同样方式工作。

4、串联 Promise

    每次对 then()或 catch() 的调用实际上创建并返回了另一个 Promise ,仅当前一个Promise 被完成或拒绝时,后一个 Promise 才会被决议。

let p1 = newPromise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value){
console.log(value);
}).then(function(){
console.log("Finished");
});

此代码输出:

42
Finished

对p1.then()的调用返回了第二个 Promise ,又在这之上调用了 then() 。仅当第一个Promise 已被决议后,第二个 then() 的完成处理函数才会被调用。

5、捕获错误

Promise链允许你捕获前一个 Promise的完成或拒绝处理函数中发生的错误

let p1 = newPromise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value){
throw newError("Boom!");
}).catch(function(error){
console.log(error.message);// "Boom!"
});

6、在Promise链中返回值

    Promise链的另一重要方面是能从一个 Promise传递数据给下一个 Promise 的能力。传递给执行器中的 resolve() 处理函数的参数,会被传递给对应 Promise 的完成处理函数,这点你前面已看到过了。你可以指定完成处理函数的返回值,以便沿着一个链继续传递数据。例如:

let p1 = newPromise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value){
console.log(value);// "42"
return value + 1;
}).then(function(value){
console.log(value);// "43"
});

也可以在 Promise 链中返回 Promise:

let p1 = newPromise(function(resolve, reject) {
resolve(42);
});
let p2 = newPromise(function(resolve, reject) {
resolve(43);
});
p1.then(function(value){
// 第一个完成处理函数
console.log(value);// 42
return p2;
}).then(function(value){
// 第二个完成处理函数
console.log(value);// 43
});

在此代码中, p1安排了一个决议 42 的作业,p1 的完成处理函数返回了一个已处于决议态的 Promise : p2 。由于 p2 已被完成,第二个完成处理函数就被调用了。而若 p2 被拒绝,会调用拒绝处理函数(如果存在的话),而不调用第二个完成处理函数。

let p1 = newPromise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value){
console.log(value);// 42
// 创建一个新的 promise
let p2 = newPromise(function(resolve, reject) {
resolve(43);
});
return p2
}).then(function(value){
console.log(value);// 43
});

在此例中,一个新的 Promise在 p1 的完成处理函数中被创建。这意味着直到 p2 被完成之后,第二个完成处理函数才会执行。若你想等待前面的 Promise 被解决,之后才去触发另一个 Promise ,那么这种模式就非常有用。

这样,我们就可以利用then进行「链式回调」,将异步操作以同步操作的流程表示出来。Promise的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。

7、创建已决的 Promise

Promise.resolve()

语法:

Promise.resolve(value);

Promise.resolve(promise);

Promise.resolve(thenable);

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

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

Promise.reject()

语法:Promise.reject(reason)

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

这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。

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

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

在此例中, Promise.resolve()调用了 thenable.then() ,确定了这个 thenable 的Promise 状态:由于 resolve(42) 在 thenable.then() 方法内部被调用,这个 thenable 的Promise 状态也就被设为已完成。一个名为 p1 的新 Promise 被创建为完成态,并从thenable 中接收到了值(此处为 42 ),于是 p1 的完成处理函数就接收到一个值为 42 的参数。

8、响应多个 Promise

ES6 提供了能监视多个 Promise 的两个方法:Promise.all() 与 Promise.race() 。

Promise.all() 方法

Promise.all() 方法接收单个可迭代对象(如数组)作为参数,并返回一个 Promise 。这个可迭代对象的元素都是 Promise ,只有在它们都完成后,所返回的 Promise 才会被完成。例如:

let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.then(function(value) {
console.log(Array.isArray(value)); // true
console.log(value[0]); // 42
console.log(value[1]); // 43
console.log(value[2]); // 44
});
promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定。
  • 当p1, p2, p3状态都变为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是resolved的顺序)存入数组,传给p的回调函数
  • 当p1, p2, p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个reject的promise的返回值,传给p的回调函数
  • 这多个 promise 是同时开始、并行执行的,而不是顺序执行
Promise.race() 方法
Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
var 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); 
});
-------output-------
resolve two
注意:在第一个promise对象变为resolve后,并不会取消其他promise对象的执行

四、总结

Promise能够在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:且能够实现链式回调,避免了地狱回调。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值