作者:胡籽@毛豆前端

Promise Introduction

首先让我们来了解一下什么是Promise。

Promise是抽象异步处理对象以及对其进行各种操作的组件。

Promise对象有以下两个特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved/Fulfilled(已完成)、Rejected(已失败)。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都是无法改变这个状态。

(2)一旦状态改变,就不会变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved或者从Pending变为Rejected

只要这两种情况发生,状态就凝固了,不会更改,一直保持结果。就算改变已发生,再对Promise对象添加回调函数,也可立即得到这个结果。

Promise对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,还提供了统一的接口,更易控制异步操作。

Promise存在的缺点:

(1) 一旦新建就会立即执行,无法中途取消;

(2) 如不设置回调函数,Promise内部抛出的错误,不会反应到外部;

(3) 当处于Pending状态,无法得知目前进展到哪个阶段(是刚开始还是即将完成)


Promise API

Constructor

Promise 类似 XMLHttpRequest,从构造函数Promise来创建新promise对象作为接口。 可使用new 来调用 Promise 的构造器进行实例化,创建 promise 对象。

let promise = new Promise((resolve, reject) => {
    // 异步处理
    // 处理结束后,调用resolve 或 reject
})

Instance Method

可通过promise.then()实例方法,为通过new生成的promise对象设置其值在resolve(成功)reject(失败) 时调用的回调函数。

promise.then(onFulfilled, onRejected)

onFulfilledonRejected 两个都为可选参数。

只对异常情况做处理,可使用promise.then(undefined,onRejected), 只指定reject时的回调函数即可。异常处理promise.catch(onRejected)可为更好的选择。

Static Method

Promise包括几个常用的静态方法:Promise.resolve()Promise.reject()Promise.all()Promise.race()

首先看一下Promise workflow

Promise工作流

示例代码:

function asyncFunc() {
    //new Promise之后,返回一个promise对象
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello Promise!')
        }, 10)
    })
}

//为promise对象设置.then() 调用返回值时的回调函数
asyncFunc().then((value) => {
    console.log(value);
}).catch((error) => {
    console.log(error);
})

asyncFunc()返回的promise对象会在setTimeout之后的10ms时被resolve,此时then 的回调函数会被调用,并输出Hello Promise!。当前情况下,catch 的回调函数不会被执行(因为promise返回了resolve)。


编写Promise代码

创建promise对象的流程如下:

(1) new Promise(fn) 返回一个promise对象

(2) 在 fn 中指定异步操作

处理结果正常:调用`resolve(处理结果值) 处理结果错误:调用reject(Error 对象)

接下来,按照上述流程用Promise来通过异步处理方式获取XMLHttpRequest(XHR)的数据。 创建一个Promise把XHR处理包装好的名为 getUrl 的函数

function getURL(URL) {
    return new Promise((resolve, reject) => {
        let req = new XMLHttpRequest();
        req.open('GET', URL, true);
        req.onload = () => {
            if (req.status === 200) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = () => {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}

// 运行示例
let url = "http://httpbin.org/get";
getURL(url).then((value) => {
    console.log(value);
}).catch((error) => {
    console.error(error);
});

getURL 只有通过XHR取得结果状态为200时调用 resolve (数据获取成功时), 其他情况则调用 reject 方法。


Promise源码

Promise.resolve

静态方法Promise.resolve 有:Promise.resolve(value)Promise.resolve(promise)Promise.resolve(theanable) 这三种形式。 都会产生一个新的 Promise , 从而可以继续 then 链式调用。

Promise.resolve = function (value) {
    return new Promise(function (resolve) {
        resolve(value);
    });
};

Promise.resolve 的实现就是new一个新的Promise实例并调用resolve方法,最后返回。

Example:

Promise.resolve('hello').then((value) => {
    console.log(value); // 'hello'
}, (value) => {
    // no called
})

var p = Promise.resolve([1, 2, 3]);
p.then((val) => {
    console.log(val); //[1, 2, 3]
})

var old = Promise.resolve(true);
var new = Promise.resolve(old);
new.then((val) => {
    console.log(val); // true
})

Promise.reject

Promise.rejectPromise.resolve 同理,只是替换成reject。

Promise.reject = function(value) {
    return new Promise(function(resolve, reject) {
        reject(value);
    })
}

Promise.all

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

实现的思路就是类似与用一个计数器,从零开始,每当一个 Promise 实例调用了 resolve ,则 +1。当计数器等于 Promise 实例的数量时,表示全部执行完,此时返回结果。

Promise.all = function (arr) {
    var args = Array.prototype.slice.call(arr);
    
    return new Promise(function (resolve, reject) {
        if(args.length === 0) return resolve([]);
        var remaining = args.length;
        
        function res(i, val) {
            if(val && (typeof val === 'object' || typeof val === 'function')){
                var then = val.then;
                if(typeof then === 'function'){
                    var p = new Promise(then.bind(val));
                    p.then(function(val) {
                        res(i, val);
                    }, reject)
                    return;
                }
                
                args[i] = val;
                if(--remaining === 0){
                    resolve(args);
                }
            }
        }
        for (var i = 0; i < args.length; i++) {
            res(i, args[i]);
        }
    })
}

源码中是做减法,跟计数器做加法的思路是一致的。new 一个新的 Promise,当触发了计数器设定的值(即 0),则调用它的 resolve,从而触发 then 函数。

res 函数里,给每一个 Promise 实例绑定一个 then 方法,当触发 resolve,即触发 then,从而再次调用 res 函数。当传入的值不再是 Promise 实例,就用 args 记录,作为返回的结果数组。并重新设置计数器 remaining(做减法)。

当 remaining 被减到 0,表示所有传入的 Promise 实例都执行了 resolve,此时可以调用新 new 出来的 Promise 实例的 resolve 。

Example:

var promise = Promise.resolve(3);
Promise.all([true, promise]).then(values => {
    console.log(values); // [true, 3]
});

Promise.race

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 与 Promise.all 不同的是:只要有一个 promise 对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

Promise.race = function (values) {
    return new Promise(function (resolve, reject) {
        values.forEach(function (value) {
            Promise.resolve(value).then(resolve, reject);
        })
    })
}

最后

把Promise 从头捋了一遍,发现源码并非想象中的那般难。。。。


毛豆前端团队

never stop, always on the way!