异步编程解决方案:Promise

前言

Promise是异步编程的经常使用方案,解决了ajax请求数据中的地狱回调问题,使代码看起来像同步同样,可读性高。本文从梳理知识点出发,参(chao)考(xi)了不少比较比如较权威的技术文对Promise的介绍以及Promise的实现方案,最后辅以大厂的常见异步面试题做为知识补充。javascript

本文目录

  • Promise 介绍与基本用法
  • Promise 静态方法(Static Method)
  • Promise 原型方法(API)
  • Promise 优缺点
  • Promise 实现
  • Async await
  • 异步相关面试题

正文

Promise介绍与基本用法

Promise是什么

Promise是异步编程的一种解决方案,它表明了一个异步操做的最终完成或者失败。从语法层面上来说,Promise是一个对象,从它能够获取异步操做的消息。前端

Promise对象有如下两个特色:java

  • 1.对象状态不受外界影响。node

    Promise对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操做的结果,以决定当前是哪种状态,任何其余操做都没法改变这个状态。jquery

  • 2.状态改变后不会再变,会一直保持这个结果。ios

    Promsie对象状态只能由 pending => fulfilled/rejected, 一旦修改就不能再变。若是状态改变已经发生了,再对Promise对象添加回调函数,也会当即获得这个结果。git

Promise基本用法

Promise是一个构造函数,new Promise 返回一个 promise 的实例对象,接收一个excutor执行函数做为参数, excutor有两个函数类型形参resolve、reject。es6

new Promise((resolve, reject) => {
    // 根据处理结果调用resolve()或reject()
})
复制代码

来看一个用Promise实现Ajax操做的栗子github

const getJSON = function(url) {
    const promise = new Promise((resolve, reject) => {
        const handler = function() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
        
        const client = new XMLHttpRequest();
        client.open('GET', url);
        client.onreadystatechange = handler;
        client.responseType = 'json';
        client.setRequestHeader('Accept', 'application/json');
        client.send();
    });
    return promise;
}

getJSON('/posts.json').then((json) => {
    console.log(`Contents:${json}`);
}, (error) => {
    console.error('出错了', error);
});
复制代码

上面代码中,getJSON是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的http请求,而且返回一个Promise对象。请求的数据状态码为200的时候,会调用resolve函数,不然调用reject函数,而且调用时都带有参数,将结果传递给回调函数。面试


Promise静态方法

咱们能够把Promise构造函数打印出来看看:

(1)Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,一般用于处理多个并行异步操做。

const p = Promise.all([p1, p2, p3]);
复制代码

Promise.all方法接受一个数组做为参数,p一、p二、p3都是 Promise 实例。(Promise.all方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。)

p的状态由p一、p二、p3决定,分红两种状况。

(1)只有p一、p二、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p一、p二、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

const p = Promise.all([p1, p2, p3]).then(data => {
    console.log(data); // [1, 2, 3] 返回的结果顺序和Promise实例数组顺序同样
}).catch(err => throw err);
复制代码
(2)Promise.race()

Promise.race()一样是将多个Promise实例包装成一个新的Promise实例,与Promise.all()不一样的是,Promise实例数组中只要有一个实例率先改变状态,新的Promise实例的回调函数就会返回那个率先改变的Promise实例的返回值。

const p = Promise.race([
    fetch('/api/user-info'),
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error('request timeout'), 5000)
    }
]);

p
    .then(res => console.log(res)) // 若是5秒内fetch方法返回结果,p的状态就会变成fulfilled,触发then方法的回调
    .catch(err => throw err); // 若是5秒后fetch方法没有返回结果,p的状态就会变成rejected,从而触发catch方法的回调
复制代码
(3)Promise.resolve()

返回一个fulfilled状态的promise对象

Promise.resolve('hello');
// 至关于
const promise = new Promise(resolve => {
   resolve('hello');
});
复制代码

参数有一下四种状况:

  • 1.Promise实例 若是参数是 Promise 实例,那么Promise.resolve将不作任何修改、原封不动地返回这个实例。
  • 2.thenable对象(具备then方法的对象) Promise.resolve方法会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。
let thenable = {
  then: function(resolve, reject) {
    resolve('jiaxin');
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // jiaxin
});
复制代码
  • 3.原始值(string, number, date等),或者是不具有then方法的对象,返回一个Promise对象
const p = Promise.resolve('Hello');

p.then(function (s){
 console.log(s); // Hello
});
复制代码
  • 4.不带任何参数,直接返回一个resolved状态的 Promise 对象。

    当即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

setTimeout(function () { // 在下一轮“事件循环”开始时执行
  console.log('three');
}, 0);

Promise.resolve().then(function () { // 在本轮事件循环结束时执行
  console.log('two');
});

console.log('one');

// one
// two
// three
复制代码
Promise.resolve();
复制代码
(4)Promise.reject()
Promise.reject(reason)方法返回一个rejected状态的Promise实例
复制代码
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s); // 出错了
});
复制代码

Promise原型上的方法

咱们把Promise的原型打印出来看看

(1)Promise.prototype.then(onFulfilled, onRejected)
onFulfilled 是用来接收promise成功的值
onRejected 是用来接收promise失败的缘由
该方法返回一个新的Promise对象,所以能够采用链式写法,即then方法后面再调用另外一个then方法。
复制代码
(2)Promise.prototype.catch(onRejected)
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。返回的也是一个新的promise对象
复制代码
// bad,不推荐
promise
    .then(data => /** success */, err => /** error */);
    
// good,推荐
promise.then(data => /** success */).catch(err => /** error */);
复制代码

上述代码中,第二种写法要好于第一种写法,理由是第二种写法能够捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。所以,建议老是使用catch方法,而不使用then方法的第二个参数。

(3)Promise.prototype.finally(onFinally)

finally方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。

promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
复制代码

finally方法的回调函数不接受任何参数,所以不知道前面的 Promise 状态究竟是fulfilled仍是rejected。这代表,finally方法里面的操做,应该是与状态无关的,不依赖于 Promise 的执行结果。

而且finally方法老是会返回原来的值


Promise优缺点

  • 优势:将异步操做以同步操做的流程表达出来,更好地解决了层层嵌套的回调地狱

  • 缺点:

    1.没法取消Promise,Promise一旦新建即当即执行,没法中途取消。
    2.若是不设置回调函数,Promise内部抛出的错误,不会反映到外部。
    3.当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。


实现Promise

/**
 * Promise 实现 遵循promise/A+规范
 * Promise/A+规范译文:
 * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
 */

// promise 三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let that = this; // 缓存当前promise实例对象
    that.status = PENDING; // 初始状态
    that.value = undefined; // fulfilled状态时 返回的信息
    that.reason = undefined; // rejected状态时 拒绝的缘由
    that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    function resolve(value) { // value成功态时接收的终值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }

        // 为何resolve 加setTimeout?
        // 2.2.4规范 onFulfilled 和 onRejected 只容许在 execution context 栈仅包含平台代码时运行.
        // 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环以后的新执行栈中执行。

        setTimeout(() => {
            // 调用resolve 回调对应onFulfilled函数
            if (that.status === PENDING) {
                // 只能由pending状态 => fulfilled状态 (避免调用屡次resolve reject)
                that.status = FULFILLED;
                that.value = value;
                that.onFulfilledCallbacks.forEach(cb => cb(that.value));
            }
        });
    }

    function reject(reason) { // reason失败态时接收的拒因
        setTimeout(() => {
            // 调用reject 回调对应onRejected函数
            if (that.status === PENDING) {
                // 只能由pending状态 => rejected状态 (避免调用屡次resolve reject)
                that.status = REJECTED;
                that.reason = reason;
                that.onRejectedCallbacks.forEach(cb => cb(that.reason));
            }
        });
    }

    // 捕获在excutor执行器中抛出的异常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

/**
 * resolve中的值几种状况:
 * 1.普通值
 * 2.promise对象
 * 3.thenable对象/函数
 */

/**
 * 对resolve 进行改造加强 针对resolve中不一样值状况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {  // 若是从onFulfilled中返回的x 就是promise2 就会致使循环引用报错
        return reject(new TypeError('循环引用'));
    }

    let called = false; // 避免屡次调用
    // 若是x是一个promise对象 (该判断和下面 判断是否是thenable对象重复 因此无关紧要)
    if (x instanceof Promise) { // 得到它的终值 继续resolve
        if (x.status === PENDING) { // 若是为等待态需等待直至 x 被执行或拒绝 并解析y值
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject);
            }, reason => {
                reject(reason);
            });
        } else { // 若是 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise
            x.then(resolve, reject);
        }
        // 若是 x 为对象或者函数
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是不是thenable对象(具备then方法的对象/函数)
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, reason => {
                    if(called) return;
                    called = true;
                    reject(reason);
                })
            } else { // 说明是一个普通对象/函数
                resolve(x);
            }
        } catch(e) {
            if(called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

/**
 * [注册fulfilled状态/rejected状态对应的回调函数]
 * @param  {function} onFulfilled fulfilled状态时 执行的函数
 * @param  {function} onRejected  rejected状态时 执行的函数
 * @return {function} newPromsie  返回一个新的promise对象
 */
Promise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    let newPromise;
    // 处理参数默认值 保证参数后续可以继续执行
    onFulfilled =
        typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected =
        typeof onRejected === "function" ? onRejected : reason => {
            throw reason;
        };

    // then里面的FULFILLED/REJECTED状态时 为何要加setTimeout ?
    // 缘由:
    // 其一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环以后的新执行栈中执行) 因此要在resolve里加上setTimeout
    // 其二 2.2.6规范 对于一个promise,它的then方法能够调用屡次.(当在其余程序中屡次调用同一个promise的then时 因为以前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),因此要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected

    // 其二 2.2.6规范 也是resolve函数里加setTimeout的缘由
    // 总之都是 让then方法异步执行 也就是确保onFulfilled/onRejected异步执行

    // 以下面这种情景 屡次调用p1.then
    // p1.then((value) => { // 此时p1.status 由pending状态 => fulfilled状态
    //     console.log(value); // resolve
    //     // console.log(p1.status); // fulfilled
    //     p1.then(value => { // 再次p1.then 这时已经为fulfilled状态 走的是fulfilled状态判断里的逻辑 因此咱们也要确保判断里面onFuilled异步执行
    //         console.log(value); // 'resolve'
    //     });
    //     console.log('当前执行栈中同步代码');
    // })
    // console.log('全局执行栈中同步代码');
    //

    if (that.status === FULFILLED) { // 成功态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
                } catch(e) {
                    reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
                }
            });
        })
    }

    if (that.status === REJECTED) { // 失败态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (that.status === PENDING) { // 等待态
        // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
        return newPromise = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};

/**
 * Promise.all Promise进行并行处理
 * 参数: promise对象组成的数组做为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的全部promise对象所有变为resolve状态的时候,才会resolve。
 */
Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        let done = gen(promises.length, resolve);
        promises.forEach((promise, index) => {
            promise.then((value) => {
                done(index, value)
            }, reject)
        })
    })
}

function gen(length, resolve) {
    let count = 0;
    let values = [];
    return function(i, value) {
        values[i] = value;
        if (++count === length) {
            console.log(values);
            resolve(values);
        }
    }
}

/**
 * Promise.race
 * 参数: 接收 promise对象组成的数组做为参数
 * 返回值: 返回一个Promise实例
 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪个更快)
 */
Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
           promise.then(resolve, reject);
        });
    });
}

// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}

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

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

/**
 * 基于Promise实现Deferred的
 * Deferred和Promise的关系
 * - Deferred 拥有 Promise
 * - Deferred 具有对 Promise的状态进行操做的特权方法(resolve reject)
 *
 *参考jQuery.Deferred
 *url: http://api.jquery.com/category/deferred-object/
 */
Promise.deferred = function() { // 延迟对象
    let defer = {};
    defer.promise = new Promise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}

/**
 * Promise/A+规范测试
 * npm i -g promises-aplus-tests
 * promises-aplus-tests Promise.js
 */

try {
  module.exports = Promise
} catch (e) {
}

复制代码

async await

async函数是ES2017引入的新语法,是Generator 函数的语法糖,使得异步操做变得更加的方便。

来看一个axios发送请求获取数据的栗子,假设每一次发送请求的param都依赖前一个ajax返回的response:

// 回调函数的形式,造成回调地狱
axios.get('url1', {
    params
}).then(data1 => {
    axios.get('url2', {
        params: data1
    }).then(data2 => {
        axios.get('url3', {
            params: data2
        }).then(data3 => {
            axios.get('url4', {
                params: data3
            }).then(data4 => {
                // ....一直回调
            })
        })
    })
});


// Promise解决回调地狱
const request = (url, params) => {
    return axios.post(url, params)
        .then(data => Promise.resolve(data))
        .catch(error => Promise.reject(error));
};

request(url1, params1)
    .then(data1 => {
        return request(url2, data1);
    }).then(data2 => {
        return request(url3, data2);
    }).then(data3 => {
        return request(url4, data3);
    }).catch(error => throw new Error(err));
    
复制代码

async函数的语法规则比较好理解,语义比起Generator函数的*和yield,要更加地清晰,async表函数里有异步操做,await表示紧跟在后面的表达式须要等待的结果。可是,async函数的难点在于误处理机制。任何一个await语句后面的Promise对象变为reject状态,那么整个async函数都会中执行。

错误处理,一种方法是将await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误。

async function f() {
    await Promise.reject('出错了')
        .catch(e => console.log(e));
    return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
// 出错了
// hello world
复制代码

另一个方法,就是统一放在try..catch结构中

async function main() {
    try {
        const val1 = await firstStep();
        const val2 = await secondStep(val1);
        const val3 = await thirdStep(val1, val2);
    
        console.log('Final: ', val3);
    }
    catch (err) {
        console.error(err);
    }
}
复制代码

总结:async函数能够看做是多个异步操做,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。


相关面试题

1.异步笔试题(头条)

请写出下面代码的运行结果

async function async1() {
    console.log('async1 start'); // 4.输出'async1 start'
    await async2(); // 5.遇到await时,会将后面的代码执行一遍
    console.log('async1 end');//7.将await后面的代码加入到microtask中的Promise队列中,跳出async1函数来执行后面的代码
    // 11.寻找到微任务队列的第一个任务,输出'async1 end'
}

async function async2() {
    console.log('async2'); // 6.输出'async2'
}

console.log('script start'); // 1.打印'script start'

setTimeout(function() {
    console.log('setTimeout');
}, 0); // 2.加入宏任务队列中

async1(); // 3.调用async1函数

new Promise(function(resolve) { // 8.Promise中的函数当即执行,输出'promise1'
    console.log('promise1');
    resolve();
}).then(function() { // 9.promise中的then被分发到microtask的Promise队列中
    console.log('promise2');
});

console.log('script end'); // 10.script任务继续执行,输出'scriptend',一个宏任务执行完毕,去检查是否存在微任务
复制代码

该题的本质,是考察setTimeout、promise、async await的实现及执行顺序,以及JS的事件循环机制。

先来了解下宏任务(macrotask)和微任务(microtask):

宏任务

宏任务(macrotask):能够理解成是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

macrotask主要包括:

(1)script(总体代码)
(2)setTimeout、setInterval
(3)I/O
(4)UI交互事件
(5)postMessage
(6)MessageChannel
(7)setImmediate(Node.js环境)
复制代码
微任务

微任务(microtask),能够理解是在当前task执行结束后当即执行的任务。也就是说,在当前task任务后,下一个task以前,在渲染以前。

microtask主要包括:

(1)promise
(2)process.nextTick(Node.js环境)
(3)MutationObserver
(4)Object.observer(废弃)
复制代码
2.setTimeout、Promise、Async/Await 的区别

1) setTimeout 属于宏任务,setTimeout 的回调函数会放在宏任务队列里,等到执行栈清空之后执行

2) promise 属于微任务,promise.then 里的回调函数会放在微任务队列里,等宏任务里的同步代码执行完再执行

3) async 方法执行时,须要await时,会当即执行表达式,而后把表达式后面的代码放到微任务队列中,让出执行栈让同步代码先执行,等当前宏任务完,再执行微任务队列里的任务

3.Async/Await 如何经过同步的方式实现异步(头条、微医)

经过 await 将异步代码改形成同步代码

看以下例子:

(async () => {
       var a = await A();
       var b = await B(a);
       var c = await C(b);
       var d = await D(c);
   })();
复制代码
// Generator
   run(function*() {
       const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
       console.log(res1);
       const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
       console.log(res2);
   });

   // async/await
   const readFile = async ()=>{
       const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
       console.log(res1);
       const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
       console.log(res2);
       return 'done';
   }
   const res = readFile();

复制代码
4.JS 异步解决方案的发展历程以及优缺点。(滴滴、挖财、微医、海康)

一、 回调函数(callback)

setTimeout(() => {
        // callback 函数体
    }, 1000)
复制代码
ajax('XXX1', () => {
        // callback 函数体
        ajax('XXX2', () => {
            // callback 函数体
            ajax('XXX3', () => {
            // callback 函数体
            })
        })
    })
复制代码

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱有什么问题?

  • 缺少顺序性: 回调地狱致使的调试困难,和大脑的思惟方式不符
  • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
  • 嵌套函数过多的多话,很难处理错误

优势:解决了同步阻塞的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

二、 Promise

Promise就是为了解决callback的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,若是咱们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

ajax('XXX1')
   .then(res => {
     // 操做逻辑
     return ajax('XXX2')
 }).then(res => {
     // 操做逻辑
     return ajax('XXX3')
 }).then(res => {
     // 操做逻辑
 })
复制代码

优势:解决了回调地狱的问题 缺点:没法取消promise,错误须要经过回调函数来捕获

三、 Generator

function *fetch() {
    yield ajax('XXX1', () => {})
    yield ajax('XXX2', () => {})
    yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
复制代码

特色:能够控制函数的执行,能够配合 co 函数库使用

  1. Async/await
async function test() {
    // 如下代码没有依赖性的话,彻底可使用 Promise.all 的方式
    // 若是有依赖性的话,其实就是解决回调地狱的例子了
    await fetch('XXX1')
    await fetch('XXX2')
    await fetch('XXX3')
}
复制代码

优势是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题。

缺点:await 将异步代码改形成同步代码,若是多个异步操做没有依赖性而使用 await 会致使性能上的下降。

5.Promise 构造函数是同步执行仍是异步执行,那么 then 方法呢?(微医)

Promise 构造函数是同步执行的,而 then 是异步执行的,then 是属于微任务。

这里看一个例子:

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve(5);
  console.log(2);
}).then(val => {
  console.log(val);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
setTimeout(function() {
  console.log(6);
});
复制代码
6.模拟实现一个 Promise.finally

finally() 方法返回一个Promise。在promise结束时,不管结果是fulfilled或者是rejected,都会执行指定的函数。为在Promise状态被改变后会执行finally中的回调。 这避免了一样的语句须要在then()和catch()中各写一次的状况。

先看promise实例finally的特性

  • 返回为thenable对象,then函数中调用reject 则返回的以此为拒因的promise
  • 返回为thenable对象,执行then抛出错误, 则返回的以此错误为拒因的promise
  • 返回为thenable对象, 调用resolve,则返回状态和值跟上层同样的promise
  • finally回调返回rejected状态的Promise, 则返回的以此为拒因的promise
  • finally回调执行若是抛出错误,则返回的以抛错为拒因的promise
  • finally回调返回fulfilled状态的promise,则返回状态和值跟上层同样的promise
  • 其余状况,则返回状态和值跟上层同样的promise

关于实现,下面第一个是基于以前resolvePromise函数实现的, 第二个是阮一峰ES6文章中的实现
阮一峰 ECMAScript6入门 - Promise.prototype.then)

Promise.prototype._finally = function (cb) {
    const that = this;
    function resolvePromise(x, resolve, reject) {
        if (x instanceof Promise) {
            x.then(() => that.then(resolve, reject), reject);
        } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
            try { // 是不是thenable对象(具备then方法的对象/函数)
                let then = x.then;
                if (typeof then === 'function') {
                    then.call(x, () => that.then(resolve, reject), reject);
                } else { // 说明是一个普通对象/函数
                    that.then(resolve, reject);
                }
            } catch (e) {
                reject(e);
            }
        } else {
            that.then(resolve, reject);
        }
    }
    return new Promise((resolve, reject) => {
        try {
            var v = cb();
            resolvePromise(v, resolve, reject)
        } catch (err) {
            reject(err);
        }
    })
}

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 })
  );
};
复制代码
7.介绍下 Promise.all 使用、原理实现及错误处理

Promise.all使用:

const p = Promise.all([p1, p2, p3]);
p.then(res => /* to do */)
 .catch(err => reject(err));
复制代码

Promise.all特性:

  • 对Promise实例进行并行处理,接收一个promise对象组成的数组做为参数
  • 当这个数组里的全部promise对象所有进入FulFilled状态时,一次性resolve一个promise实例对象,
  • 当数组里只要存在一个reject状态的promise对象,则返回失败的信息
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let values = [];
        let count = 0;
        promises.forEach((promise, index) => {
            promise.then(value => {
                values[index] = value;
                count++;
                if (count === promises.length) resolve(values)
            }, reject)
        })
    }
}
复制代码

Promise.all错误处理

  • 1.所有改成串行调用(失去了node并发优点)
  • 2.当promise捕获到error的时候,代码吃掉这个异常,返回resolve,约定特殊格式表示这个调用成功了
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 0)
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 200)
});

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        try {
            console.log(ABC.efd);
        } catch (err) {
            resolve('error') // 这里是关键
        }
    },100)
});

Promise.all([p1, p2, p3]).then((results) => {
    console.log('success');
    console.log(results);
}).catch((error) => {
    console.log('err');
    console.log(error);
})

// Output: 'success' [1, 2. 'error']
复制代码
8.设计并实现 Promise.race()
const p = Promise.race([p1, p2, p3]);
复制代码

Promise.race()特性:

  • 与all同样,参数为多个Promise实例对象组成的数组
  • 与all不同的是,Promise.race是只要实例数组中有一个实例率先改变状态,就会把这个率先改变状态的promise的返回值传递给p的回调函数。 Promise.race的实现
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        promises.forEach((promise) => {
            promise.then(resolve, reject);
        })
    })
}
复制代码

参考资料

木易杨前端进阶 每日壹题

阮一峰 ECMAScript6入门 Promise对象

Promise MDN

浏览器的Tasks、microtasks、queues和schedules

阮一峰 ECMAScript6入门 async函数

混沌传奇 从零一步一步实现一个完整版的Promise

Promise A+ 规范

相关文章
相关标签/搜索