一、定义
ECMA-262(ES6)给出的定义是:
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
翻译成中文就是:Promise对象是用来作为延迟(包含异步)运算的最终结果的占位符。
标准给出的定义是抽象的,我给出的定义是:
Promise对象是一个异步操作的容器,是一个包含对未来事件的结果(可以是异步的),是一个穿越时间循环存在的对象。Promise有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。并且Promise是异步编程的解决方案,或者说主要是用同步的方式来书写异步。
基本的概念本篇教程不再复述,本篇不是入门教程,如果你此前不了解Promise,请先自行了解。
二、关于Promise
1.首先Promise是一个构造函数,可以生成Promise实例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
2.Promise.prototype.then()和Promise.prototype.catch()
(1)resolved状态的Promise会回调后面的第一个then;
(2)rejected状态的promise会回调后面的第一个catch。
(3) then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。
/**
* 用setTimeout模拟异步操作
*/
const promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(1);
}, 500)
})
promise
.then(function (result) {
console.log(result)
})
.catch(function (err) {
})以上代码先返回一个pending状态的Promise,500ms后输出1,这也是一个事件循环,稍后会介绍。
3.任何一个rejected状态且后面没有catch的Promise都会造成浏览器(或者node)环境的全局错误,执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch回调函数的执行结果决定。
(1)若回调函数的最终结果是throw,则该promise是rejected状态;
(2)若回调函数的最终结果是return,则该Promise是resolved状态;
(3)若回调函数最终return了一个Promise,该Promise会和回调函数return的Promise的状态保持一致。
4.如果是多次异步调用,需要多个then,但是只需要一个catch。
任何一个then中返回的promise对象rejected都会被catch捕获。
/**
* promise的链式调用
*/
function getResult(round) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.2) {
const error = new Error('failed');
error.round = round;
reject(error);
} else {
resolve('success');
}
}, 500)
})
}
getResult(1)
.then(()=> {
return getResult(2);
})
.then(()=> {
return getResult(3);
})
.then(()=> {
if (Math.random() > 0.1) {
const error = new Error('keyboard')
error.round = 'keyboard'
throw error
}
})
.catch((err)=> {
console.log('cry at ' + err.round)
})
5.并发异步Promise.all,第一个catch只能捕获第一个失败的结果。
Promise.all([
getDecide('father').catch(() => { }),
getDecide('mother'),
getDecide('wife'),
]).then(() => {
console.log('family all agree')
}).catch((err) => {
console.log(err.name + ' not agree');
})
/**
*获取是否同意结果
*/
function getDecide(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.2) {
const error = new Error('disagree');
error.name = name;
reject(error);
} else {
resolve('agree');
}
}, Math.random() * 400)
})
}6.Promise.prototype.finally
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
7.Promise.race
当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolveed),也可以是失败(rejected),这要取决于第一个完成的方式是两个中的哪个。
如果传的参数数组是空,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) => {
resolve(data);
return;
}, (err) => {
reject(err);
return;
});
}
}
});
}
8.async/await的执行结果是一个Promise,是Promise的语法糖。async/await是异步编程的终极解决方案,以同步的方式写异步。
(1)使用try/catch可以捕获await的错误
(2)await可以以同步的写法获取Promise的执行结果
(3)await可以“暂停”async function的执行
(4)如果要并行异步,await Promise.all([异步1,异步2,...])
三、事件循环中的Promise
事件循环指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。"Event Loop是一个程序结构,用于等待和发送消息和事件。是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。
异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
事件循环中的异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
宏任务(macrotask):
script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js环境)
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
- 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
- 更新render(每一次事件循环,浏览器都可能会去更新渲染)
- 重复以上步骤
Promise属于微任务,所以会在执行完宏任务后执行。本文提到的一个例子:
const promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(1);
}, 500)
})
promise
.then(function (result) {
console.log(result)
})
.catch(function (err) {
})上面代码的执行结果是:先返回一个pending状态的Promise,然后输出1。
四、Promise A+规范的实现
根据promise A+规范实现promise,promise A+规范
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function Promise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function(value) {
if (value instanceof Promise) {
// 如果 value 是个 Promise,递归执行
return value.then(_this.resolve, _this.reject);
}
setTimeout(() => {
// 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED;
_this.value = value;
_this.resolvedCallbacks.forEach(cb => cb());
}
});
};
_this.reject = function(reason) {
setTimeout(() => {
// 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.rejectedCallbacks.forEach(cb => cb());
}
});
};
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
Promise.prototype.then = function(onResolved, onRejected) {
let self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
let promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === "function" ? onResolved : v => v;
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason };
if (self.currentState === RESOLVED) {
return (promise2 = new Promise(function(resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function() {
try {
let x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
// 异步执行onRejected
try {
let x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === PENDING) {
return (promise2 = new Promise(function(resolve, reject) {
self.resolvedCallbacks.push(function() {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
let x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function() {
try {
let x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof Promise) {
if (x.currentState === PENDING) {
x.then(function(value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
module.exports = Promise;如果要对上面的promise实现测试,可以添加如下代码:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}安装promises-aplus-tests并执行测试
npm install -g promises-aplus-tests
promises-aplus-tests promise.js结果截图,即通过了872个测试用例:

五、参考资料
1.ECMAScript® 2015 Language Specification
2.Promise 对象,「阮一峰官方博客」
3.Promise,「廖雪峰官方博客」
6.promise,「MDN」