异步的开端-promise

当 promise 已经成为日常开发必不可少的一部分。
你是否了解为什么要使用promise?promise 的执行机制?以及 promise 的发展历史?

1. 为什么使用 promise ?

先看一段代码:

const fs = require('fs'); fs.readFile('./name.txt', 'utf-8', (err, data) => { if (err) { console.log(err); return; } if (data) { fs.readFile(data, 'utf-8', (err, data) => { fs.readFile(data, 'utf-8', (err, data) => { console.log(data); }); }); } });

在没有 promise 时,我们使用回调的方式来管理异步,使用大量的回调函数嵌套后,
让我们的程序变的越来越臃肿,难于维护,功能无法拓展。

再看一段代码:

try { console.log(a); } catch (error) { console.log(error.message); // a is not defined }

使用 try-catch 时,我们可以优雅的捕获错误,然后在其中掺杂异步时,会发生什么?

try { setTimeout(() => { console.log(a); }, 30) } catch (error) { console.log(error.message); // 直接报错,无法捕获错误 }

是的,你没看错,try-catch 是无法捕获异步代码的错误的,它只能捕获同步代码的错误。
还有回调是无法处理同步并发问题的,以上就是我们常说的回调地狱问题。

2. 如何解决回调地狱问题?

在 jQuery 中使用 Callbacks 进行回调管理。

let cb = $.Callbacks(); function a (x, y) { console.log('a', x, y); } function b (x, y) { console.log('b', x, y); } cb.add(a, b); cb.fire(10, 20);

包括后面出现的 deferer,也是使用 Callbacks 管理回调。

function waitHandle () { var dtd = $.Deferred(); var wait = function () { var task = function () { dtd.resolve(); } setTimeout(task, 2000); returen dtd; } return wait(dtd); } var w = waitHandle(); w .done(function () { console.log('ok') }) .fail(function () { console.log('error'); }); w .then( function () { }, function () { } ) w.reject(); // 异步失败后,全部都是失败。可以在外层显示调用reject方法。

jQuery 中处理异步时,前期使用的是 Callbacks,后面又出现 Deferred。
而 Deferred 就是 Promise 最初的形式,目前我们解决回调地狱的手段就是 Promise。

3. ES6中的Promise

promise 是一种规范,不止 jQuery 实现了 promise,还有很多库也实现了 promise,都遵循 promise A+ 规范。例如 bluebird 就是使用promiseA+规范定义的工具包。

先看一个Promise的使用方式。

// executor 执行者 new Promise(function (resolve, reject) { console.log('promise'); }); console.log(1); // promise // 1

从上面的代码中可以看到,传入 Promise 对象中的函数是与外部的打印是同步执行的。
实例化的 Promise 存放以后才会结束的事件,可以被当作一个容器。

(1)promise 的三种状态

1. pending 进行中
2. fufilled(resolve)已成功
3. reject 已失效

(2)promise 的状态转换

promise 异步操作的状态是不受外界影响的,同时,promise 的状态是不可逆的,一旦状态发生变化,不会再改变。promise 的状态固化之后,再对 promsie 对象添加回调函数,可以直接拿到结果,如果是事件的话,一旦错过,就真的错过了。

pending -> resolve
pending -> reject

(3)如何使用 promise

promise 通过函数调用的方式来改变自身状态。

new Promise(function (resolve, reject) { console.log('promise'); reject(); });

promise 存在两种使用方式。

第一种:

使用 .then 的方式,.catch 的方式。

const promise = new Promise(function (resolve, reject) { setTimeout(function () { Math.random() * 100 > 60 ? resolve() : reject(); }, 300) }); promise .then(res => { console.log('函数执行完成'); }) .catch(err => { console.log('函数执行失败'); }); promise.then( () => { console.log('函数执行完成'); }, () => { console.log('函数执行失败'); } );

第二种:

只使用 .then,在 then 属性中传入两个回调函数。

const promise = new Promise((resolve, reject) => { console.log(0); resolve(1); }); promise.then( (val) => { console.log(val); }, (reason) => { console.log(reason); } );

promise 的 resolve 和 reject 是异步操作,调用的是异步的回调函数。

(4)关于 setTimeout 与 promise

setTimeout(() => { console.log('time'); }); const promise = new Promise((resolve, reject) => { console.log(0); resolve(1); }); promise.then( (val) => { console.log(val); }, (reason) => { console.log(reason); } ); console.log(2); // 0 // 2 // 1 // time

上面打印的结果是 0 2 1 time。

由此也不得不说一下,JS代码中存在两种任务,宏任务和微任务。

微任务:

promsie、process.nextTick...

宏任务:

setTimeout、setInternal...

JS执行时,存在宏任务队列和微任务队列,每一次事件轮询时,当主线程任务完成后,会调用任务队列中的回调函数推入到执行栈中。优先放入微任务队列中的回调函数,然后再处理宏任务队列中的回调函数。

再来看下面一段代码。

Promise.resolve().then( () => { console.log('promise1'); setTimeout(() => { console.log('setTimeout2') }) } ); setTimeout(() => { console.log('setTimeout1'); Promise.resolve().then(() => { console.log('promise2'); }); }); // promise1 // setTimeout1 // promise2 // setTimeout2 // promise1 // setTimeout1 // setTimeout2 // promise2

这段代码会出现两种运行结果,这和事件轮询有关。宏任务和微任务是针对异步函数进行划分的,对于同步代码来说,并不存在这两种概念。
如何想了解更多的相关概念,可以去看我之前写的一篇博文《剖析JS执行机制》,在这里就不反复解读了。

(5)promise 的链式调用问题

promise 链式调用时,第二个调用的函数的形参是上次 then 函数返回的值。
即第一次 then 的返回值作为下一次 then 的执行参数。

let promise = new Promise((resolve, reject) => { setTimeout(() => { Math.random() * 100 > 60 ? resolve('ok') : reject('no'); }); }); promise .then( (val) => { console.log(val); }, (reason) => { console.log(reason); } ) .then( (val) => { console.log('second', val); }, (reason) => { console.log('second', reason); } ) // ok // second undefined // no // second no
let promise = new Promise((resolve, reject) => { resolve(1); }); promise .then( (val) => { console.log(val); return 2; } ) .then( (val) => { console.log('second', val); } ); // 1 // second 2

在链式调用中,then 函数中可以继续返回 promise 对象。

let promise = new Promise((resolve, reject) => { resolve(1); }); promise .then( (val) => { console.log(val); return new Promise((resolve, reject) => { reject(2); }); } ) .then( (val) => { console.log('success', val); }, (err) => { console.log('error', err); } ); // 1 // error 2

4. 总结

本篇文章介绍了 promsie 的由来,以及如何简单的使用 promise。
后面将对 promise 进行进一步的介绍,以及自己实现一个 Promise 对象。