[es6系列]进修Promise

本文是基于对阮一峰的Promise文章的进修整顿笔记,整顿了文章的递次、增加了更多的例子,使其更好邃晓。

1. 概述

在Promise之前,在js中的异步编程都是采用回调函数和事宜的体式格局,然则这类编程体式格局在处置惩罚庞杂营业的状况下,很轻易涌现callback hell(回调地狱),使得代码很难被邃晓和保护。

Promise就是改良这类状况的异步编程的解决方案,它由社区最早提出和完成,es6将其写进了言语规范,一致了用法,而且供应了一个原生的对象Promise

2. 邃晓Promise

我们经由过程一个简朴例子先来感受一下Promise。

var p = new Promise(function (resolve, reject) {
    // ...
    if(/* 异步操纵胜利 */){
        resolve(ret);
    } else {
        reject(error);
    }
});

p.then(function (value) {
    // 完成态
}, function (error) {
    // 失利态
});

我们须要关注的是

  • Promise的组织函数

  • resolve() , reject()

  • then()

2.1 Promise组织函数

我们在经由过程Promise组织函数实例化一个对象时,会通报一个函数作为参数,那末这个函数有什么特性?

答案就是在新建一个Promise后,这个函数会马上实行。

let promise = new Promise(function (reslove, reject) {
    console.log('Promise');
});

console.log('end');

实行结果以下:

《[es6系列]进修Promise》

能够看到是先输出了Promise,再输出了end

2.2 resolve/reject

在Promise中,对一个异步操纵做出了笼统的定义,Promise操纵只会处在3种状况的一种,他们之间的转化如图所示

《[es6系列]进修Promise》

注重,这类状况的改变只会涌现从未完成态向完成态或失利态转化,不能逆反。完成态和失利态不能互相转化,而且,状况一旦转化,将不能变动。

只需异步操纵的结果能够决议当前是哪种状况,任何其他操纵都没法改变这个状况。这也是Promise这个名字的由来,它的英语意义是许诺,示意其他手腕没法改变。

在声明一个Promise对象实例时,我们传入的匿名函数参数中:

  • resolve就对应着完成态以后的操纵

  • reject对应着失利态以后的操纵

2.3 then()

那末题目来了,then()要领有什么作用?resolve和reject又是从那里通报过来的?

实在这两个题目是一个题目,在实例化一个Promise对象以后,我们挪用该对象实例的then()要领通报的两个参数中:

  • 第一个参数(函数)对应着完成态的操纵,也就是resolve

  • 第二个参数(函数)对应着失利态的操纵,也就是reject

那就是说,在Promise中是经由过程then()要领来指定处置惩罚异步操纵结果的要领。

2.4 现实案例

到这里我们邃晓了Promise的语法,也相识了Promise中函数是怎样实行的,连系一个现实的案例,来加深对Promise的邃晓。

我们来完成一个异步加载图片的函数

function loadImageAsync(url) {
    return new Promise(function (reslove, reject) {
        var img = new Image();
        img.onload = function () {
            reslove();
        }
        img.onerror = function () {
            reject();
        }
        console.log("loading image");
        img.src = url;
    });
}
var loadImage1 = loadImageAsync("https://www.google.co.jp/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
loadImage1.then(function success() {
    console.log("success");
}, function fail() {
    console.log("fail");
});

var loadImage2 = loadImageAsync("1.png");
loadImage2.then(function success() {
    console.log("success");
}, function fail() {
    console.log("fail");
});

我们在chrome中实行,先是通报一个有用的url,再通报一个无效的url,实行的结果为:

《[es6系列]进修Promise》

3. Promise进阶

3.1 resolve/reject的参数

reject函数的参数一般来说是Error对象的实例,而resolve函数的参数除了一般的值外,还多是另一个Promise实例,示意异步操纵的结果有多是一个值,也有多是另一个异步操纵。

var p1 = new Promise( function(resolve, reject) {
    // ...
});

var p2 = new Promise( function(resolve, reject) {
    // ...
    resolve(p1);
});

代码剖析:p1和p2都是Promise的实例,p2中的resolve要领将p1作为参数,即一个异步操纵的结果是返回另一个异步操纵。

注重,这时刻p1的状况就会通报给p2,也就是说,p1的状况决议了p2的状况,他们之间的关联是

《[es6系列]进修Promise》

举个例子

console.time('Promise example start')
var p1 = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('hi'), 3000);
});

var p2 = new Promise( (resolve, reject) => {
    setTimeout(() => resolve(p1), 10);
});

p2.then( ret => {
    console.log(ret);
    console.timeEnd('Promise example end')
});

我们在node环境下运转以上代码,实行结果为:

《[es6系列]进修Promise》

从实行时间能够看到,p2会守候p1的实行结果,然后再实行,从输出hi能够看到p1完成状况改变以后,通报给resolve(或许reject)的结果会通报给p2中的resolve

3.2 then()

从上面的例子,我们能够相识到then()要领是Promise实例的要领,即Promise.prototype上的,它的作用是为Promise实例增加状况改变时的回调函数,这个要领的第一个参数是resolved状况的回调函数,第二个参数(可选)是rejected状况的回调函数。

那末then()要领的返回值是什么?then要领会返回一个新的Promise实例(注重,不是本来谁人Promise,本来谁人Promise已许诺过,此时继承then就须要新的许诺~~),如许的设想的优点就是能够运用链式写法。

另有一个点,就是链式中的then要领(第二个最先),它们的resolve中的参数是什么?答案就是前一个then()中resolve的return语句的返回值。

来一个示例:

var p1 = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p1.then( ret => {
    console.log(ret);
    return 'then1';
}).then( ret => {
    console.log(ret);
    return 'then2';
}).then( ret => {
    console.log(ret);
});

在node环境下实行,实行结果为:

《[es6系列]进修Promise》

3.3 catch()毛病处置惩罚

catch()要领是Promise实例的要领,即Promise.prototype上的属性,它现实上是.then(null, rejection)的简写,用于指定发作毛病时的回调。

这个要领实在很简朴,在这里并不想议论它的运用,而是想议论的是Promise中的毛病的捕抓和处置惩罚。

3.3.1 Error对象的通报性

Promise对象的Error对象具有冒泡性子,会一向向后通报,直到被捕捉为止。也就是说,毛病老是会被下一个catch语句捕捉,示例代码以下:

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 能够捕抓到前面的涌现的毛病。
    console.log(err.toString());
});

实行结果以下

《[es6系列]进修Promise》

在第一个then中抛出了一个毛病,在末了一个Promise对象中能够catch到这个毛病。

由于有这类轻易的毛病处置惩罚机制,所以一般来说不要在then要领内里定义reject状况的回调函数, 而是运用catch要领

3.3.2 vs try/catch

跟传统的try/catch差别的是,假如没有运用catch要领指定毛病处置惩罚回调函数,则Promise对象抛出的毛病不会通报到外层代码(在chrome会报错)

Node.js有一个unhandledRejection事宜,特地监听未捕捉的reject毛病。以下代码就是在node环境下运转。

var p = new Promise((resolve, reject) => {
    resolve(x + 2);
});
p.then( () => {
    console.log('nothing');
});

《[es6系列]进修Promise》

3.3.3 catch()的返回值

没错,既然catch()是.then(null, rejection)的别号,那末catch()就会返回一个Promise对象,因而在后面还能够接着挪用then要领,示例代码以下:

var p = new Promise((resolve, reject) => {
    resolve(x + 2);
});
p.then( () => {
    console.log('nothing');
}).catch( err => {
    console.log(err.toString());
    return 'catch';
}).then( ret => {
    console.log(ret);
});

《[es6系列]进修Promise》

当失足时,catch会先处置惩罚之前的毛病,然后经由过程return语句,将值继承通报给后一个then要领中。

假如没有报错,则跳过catch,示例以下:

var p = new Promise((resolve, reject) => {
    resolve('p');
});
p.then( ret => {
    console.log(ret);
    return 'then1';
}).catch( err => {
    console.log(err.toString());
    return 'catch';
}).then( ret => {
    console.log(ret);
});

《[es6系列]进修Promise》

4. Promise对象要领

4.1 Promise.all()

Promise.all()要领用于将多个Promise实例,包装成一个新的Promise实例,比方

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

新的Promise实例p的状况由p1, p2, p3决议:

  • p1, p2, p3的状况都为完成态时,p为完成态。

  • p1, p2, p3中任一一个状况为失利态,则p为失利态。

4.2 Promise.race()

Promise.race要领同样是将多个Promise实例,包装成一个新的Promise实例。

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

差别的是,只需p1, p2, p3中恣意一个实例领先改变状况,则p的状况就随着改变,而且状况由领先改变的实例决议。

var p = Promise.race([
    new Promise(resolve => {
        setTimeout(() => resolve('p1'), 10000);
    }),
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error('time out')), 10);
    })
]);
p.then( ret => console.log(ret))
    .catch( err => console.log(err.toString()));

《[es6系列]进修Promise》

4.3 Promise.resolve()

Promise.resolve()能够将现有的对象转为Promise对象。

var p = Promise.resolve('p');

// 相当于
var p = new Promise(resolve => resolve('p'));

比较有意义的是Promise.resolve()会依据参数范例举行响应的处置惩罚,分几种状况议论。

4.3.1 Promise实例

参数是一个Promise实例,那末Promise.resolve将不做任何处置惩罚,直接返回这个实例。

4.3.2 thenable对象

参数是一个thenable对象,也就是说对象是具有then要领的对象,但不是一个Promise实例(就跟类数组和数组的关联一样),比方

let thenable = {
    then : function (resolve, reject) {
        resolve(42);
    }
};

let p = Promise.resolve(thenable);
p.then( ret => console.log(ret)); // 42

Promise.resolve要领会将这个对象转为Promise对象,然后马上实行thenable对象中的then要领,由于例子中的thenable对象的then要领中实行了resolve,因而会输出结果42

4.3.3 其他参数

假如参数是一个原始值,或许不具有then要领的对象,则Promise.resolve要领返回一个新的Promise对象,状况为resolve,然后直接将该参数通报给resolve要领。

var p = Promise.resolve("p");
p.then( ret => console.log(ret)); // p

4.3.4 不带任何参数

Promise.resolve要领不带参数时,会直接返回一个resolve状况的Promise对象。

须要注重的马上resolve的Promise对象,是在本轮事宜轮回的结束时,而不是下一轮事宜轮回的最先实行。示例代码:

setTimeout(() => console.log('3'), 0);
var p = Promise.resolve();
p.then(() => console.log('2'));
console.log('1');

输出结果为:

《[es6系列]进修Promise》

4.4 Promise.reject()

Promise.reject()返回一个新的Promise实例,该实例的状况为rejected,关于传入的参数的处置惩罚跟Promise.resolve相似,就是状况都为rejected

5. 两个有用的要领

5.1 done()

Promise对象的回调链,不论以then要领或许catch要领末端,如果末了一个要领抛出毛病,都有能够没法捕捉到,由于Promise内部的毛病不会冒泡到全局,因而,我们能够供应一个done要领,老是处置惩罚回调链的尾端,保证抛出任何能够涌现的毛病。

这个代码的完成异常简朴

Promise.prototype.done = function (resolve, reject) {
    this.then(resolve, reject)
        .catch( function (reason) {
            // 抛出一个全局毛病
            setTimeout( () => { throw reason }, 0);
        });
}

// 运用示例
var p = new Promise( (resolve, reject) => {
    resolve('p');
});
p.then(ret => {
    console.log(ret);
    return 'then1';
}).catch( err => {
    console.log(err.toString());
}).then( ret => {
    console.log(ret);
    return 'then2';
}).then( ret => {
    console.log(ret);
    x + 2;
}).done();

《[es6系列]进修Promise》

这里为何能够在全局抛出一个毛病?缘由就是setTimeout中的回调函数是在全局作用域中实行的,因而抛出的毛病就是在全局作用域上。

5.2 finally()

finally要领用于指定不论Promise对象末了的状况怎样,都邑实行的操纵,它与done要领最大的区分就是,它接收一个一般函数作为参数,该函数不论怎样都必须实行。

Promise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
        ret => P.resolve(callback()).then( () => ret),
        err => P.resolve(callback()).then( () => {throw reason })
    );
};

5. Promise的优劣势

从上面几个小节综合来看,能够看到Promise实在就是做了一件事变,那就是对异步操纵举行了封装,然后能够将异步操纵以同步的流程表达出来,避免了层层嵌套的回调函数,同时供应一致的接口,使得掌握异步操纵越发轻易。

然则,Promise也有一些瑕玷:

  • 没法作废Promise,一旦新建它就会马上实行,没法半途作废。

  • 假如不设置回调函数,Promise内部的毛病不会回响反映到外部。

  • 当处于未完成态时,没法得知现在希望到哪个阶段。

6. Promise与generator的连系

运用Generator函数来治理流程,碰到异步操纵的时刻,一般返回一个Promise对象。

function getFoo() {
    return new Promise( resolve => resolve('foo'));
}

var g = function * () {
    try {
        var foo = yield getFoo();
        console.log(foo);
    } catch(e){}
}

function run(generator) {
    var it = generator();

    function go(result) {
        if(result.done) return result.value;

        // 默许value是一个Promise,实在这里应该做推断的
        if(!(result.value instanceof Promise)){
            throw Error('yield must follow an instanceof Promise');
        }
        return result.value.then(
            ret => go(it.next(ret))
        ).catch(err => go(it.throw(err)));
    }

    go(it.next());
}

run(g);

上面代码的Generator函数g当中,有一个异步操纵getFoo,它返回的就是一个Promise对象。函数run用来处置惩罚这个Promise对象,并挪用下一个next要领。

7. 泉源

个人博客

    原文作者:zhangguixu
    原文地址: https://segmentfault.com/a/1190000007685095
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞