前言
promise的前世今生:
javascript的异步编程 —— callback —— Promise —— Generator(yield)—— co —— async await
在JavaScript中,遇到setTimeout,ajax,事件这些需要等待的情况,都是异步执行的,因为他不会一直等着,这样会阻塞后续代码的执行,例如:
function a(){
setTimeout(function(){
console.log(1)
},0)
}
function b(){
console.log(2)
}
a()
b()
执行结果:
2
1
复制代码
如果我们一定要先打印 1 怎么办?
function a(){
setTimeout(function(){
console.log(1)
b()
},0)
}
function b(){
console.log(2)
}
a()
执行结果:
1
2
复制代码
这就叫做回调函数,callback,这是简单的例子,在实际开发过程中,如果遇到复杂的逻辑,你可能要不停的回调,回调,回调,这样的弊端也是显而易见的,于是,es6规范,promise诞生了,下面我们就来看看promise的基本用法。
1、Promise的基本用法
let promise=new Promise((resolve,reject)=>{
let flag=false
if(flag){
resolve('成功了')
}else{
reject('失败了')
}
})
promise.then(data=>{
console.log("s",data)
},err=>{
console.log("e",err)
})
复制代码
我们看到,它其实就是个构造函数,为了深度理解promise的原理,我们来模拟一下,我把它命名为P:
class P{
constructor(executor){
//promise的三个状态,resolved、rejected、penging,
//默认是等待态
this.status='pending';
//参考:https://promisesaplus.com/
this.value=undefined;
this.reason=undefined;
//成功的回调
this.onResolvedCallbacks=[]
//失败的回调
this.onRejectedCallbacks=[]
let resolve=(data)=>{
//状态是“等待态”才能转换状态,执行方法,因为成功了就不能失败,失败了就不能成功
if(this.status === 'pending'){
this.value=data;
this.status='resolved'
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
let reject=(reason)=>{
if(this.status === 'pending'){
this.reason=reason
this.status='rejected'
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
try {
executor(resolve,reject)
}catch(e){
reject(e)
}
}
then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的
//状态为成功的时候就执行成功的方法,并且把值传给他
if(this.status == 'resolved'){
onFulFilled(this.value)
}
//失败的状态,就把失败的原因传出来
if(this.status == 'rejected'){
onRejected(this.reason)
}
}
}
module.exports=P
复制代码
试用一下:
let P=require('./myPromise')
let p=new P((r,j)=>{
console.log("1")
setTimeout(function(){
r('3') //这里不会执行,应为状态是“等待态”,既不成功,也不失败
},1000)
})
p.then(data=>{
console.log(data)
},err=>{
console.log(err)
})
//执行
1
复制代码
执行结果中我们看到 setTimeout 中的代码没有执行,那么我们接着写如果状态为“等待态”的时候该怎么执行:
then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的
//状态为成功的时候就执行成功的方法,并且把值传给他
if(this.status == 'resolved'){
onFulFilled(this.value)
}
//失败的状态,就把失败的原因传出来
if(this.status == 'rejected'){
onRejected(this.reason)
}
//等待态,既没有成功,也没有失败
if(this.status == 'pending'){
//存放成功的回调
this.onResolvedCallbacks.push(()=>{
onFulFilled(this.value)
})
//存放失败的回调
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason)
})
}
}
复制代码
再执行:
let P=require('./myPromise')
let p=new P((r,j)=>{
console.log("1")
setTimeout(function(){
r('3')
},1000)
})
p.then(data=>{
console.log("success:"+data)
},err=>{
console.log("failed:"+err)
})
打印结果:
1
success:3
复制代码
2、promise的链式调用
说起链式调用,我们可以联想到 jquery,jquery 能够实现链式调用是因为它每次调用后都返回了this,实例本身,而 promise 呢,不能靠 this ,却是在 then 方法里返回的一个新的 promise ,一个道理的。 代码如下:
let promise=new Promise((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
return new Promise((resolve,reject)=>{
resolve("2")
})
},err=>{
console.log("err1:",err)
}).then(data=>{
console.log("data2:",data) //2
return new Promise((resolve,reject)=>{
//resolve("3")
reject('failed')
})
},err=>{
console.log("err2:",err)
}).then(data=>{
console.log("data3:",data) //
return new Promise((resolve,reject)=>{
resolve("4")
})
},err=>{
console.log("err3:",err)
}).then(data=>{
console.log("data4:",data) //undefined
return new Promise((resolve,reject)=>{
resolve("5")
})
},err=>{
console.log("err4:",err)
}).then(data=>{
console.log("data5:",data)
})
打印结果:
data1: 1
data2: 2
err3: failed
data4: undefined
5
复制代码
由此可见:
1)promise 链中,每个then返回的新 promise 都是独立的,有成功的回调和失败的回调;
2)每个then的参数都来源于上一个 promise 的执行,如果上一个 promise 成功了,那么成功的 data 就会传到下一个promise的成功回调中,如果上一个 promise 失败了,那么它会走失败的回调,此时下一个 promise 的失败回调中会接收到相应的参数,而成功的回调中接收到的是 undefined ,但是promise链继续往下执行,不受任何影响;
3)如果根据实际需求,我们只需要按顺序执行方法,也可以不传参哦
如果我们换个写法:
let promise=new Promise((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
return Promise.resolve("2")
})
.then(data=>{
console.log("data2:",data) //2
return Promise.reject('failed')
})
.then(data=>{
console.log("data3:",data) //undefined
return Promise.resolve("4")
})
.then(data=>{
console.log("data4:",data) //4
return Promise.resolve("5")
})
.then(data=>{
console.log("data5:",data)
})
.catch(e=>{
console.log('e: ', e);
})
打印:
data1: 1
data2: 2
e: failed
复制代码
let promise=new Promise((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
return Promise.resolve("2")
})
.then(data=>{
console.log("data2:",data) //2
return Promise.reject('failed')
})
.catch(e=>{
console.log('e: ', e);
})
.then(data=>{
console.log("data3:",data) //undefined
return Promise.resolve("4")
})
.then(data=>{
console.log("data4:",data) //4
return Promise.resolve("5")
})
.then(data=>{
console.log("data5:",data)
})
打印:
data1: 1
data2: 2
e: failed
data3: undefined
data4: 4
data5: 5
复制代码
由此我们可以得出:如果某个 then 方法里面的 promise 状态改变为了 rejected,则promise 方法连会跳过后面的 then 直接执行 catch。
接下来,我们继续完成我们自己的promise:
function resolvePromise(promise2,x,resolve,reject){
//判断x 是不是promise
//规范里规范了一段代码,这个代码可以实现我们的promise和别人的promise进行交互
if(promise2 === x){
//不能自己等待自己完成
return reject(new TypeError('循环引用'))
}
// x 不能是null 而是对象或者 函数
if(x !== null && (typeof x === 'object' || typeof x === 'function' )){
let called; //防止成功后调用失败
try{ //防止取then时出现异常 Object.defineProperty
let then = x.then // 取x的then方法 {then:{}}
if(typeof then === 'function'){ //如果then是函数 就认为它是promise
// call 第一个参数是this ,后面的是成功的回调和失败的回调
then.call(x, y => { // 如果y(成功的回调)是promise就继续递归解析promise
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
}, r => { // 只要失败了就失败了
if (called) return;
called = true;
reject(r);
});
}else{
// then是一个普通对象,就直接成功即可
resolve(x);
}
}catch(e){ // 失败
if (called) return;
called = true;
reject(e);
}
}else {
resolve(x); // x就是一个普通值 如:x = 123
}
}
class P{
constructor(executor){
//promise的三个状态,resolved、rejected、penging,
//默认是等待态
this.status='pending';
//参考:https://promisesaplus.com/
this.value=undefined;
this.reason=undefined;
//成功的回调
this.onResolvedCallbacks=[]
//失败的回调
this.onRejectedCallbacks=[]
let resolve=(data)=>{
if(this.status === 'pending'){ //状态是“等待态”才能转换状态,执行方法,因为成功了就不能失败,失败了就不能成功
this.value=data;
this.status='resolved'
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
let reject=(reason)=>{
if(this.status === 'pending'){
this.reason=reason
this.status='rejected'
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
try {
executor(resolve,reject)
}catch(e){
reject(e)
}
}
then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的
let promise2;
//状态为成功的时候就执行成功的方法,并且把值传给他
if(this.status == 'resolved'){
promise2 = new Promise((resolve, reject) => {
// 成功的逻辑 失败的逻辑
let x = onFulFilled(this.value);
// 看x是不是promise 如果是promise 取他的结果 作为promise2,成功的结果
// 如果要是返回一个普通值 作为promise2,成功的结果
// resolvePromise可以解析x和promise2之间的关系
resolvePromise(promise2, x, resolve, reject);
});
}
//失败的状态,就把失败的原因传出来
if(this.status == 'rejected'){
promise2 = new Promise((resolve, reject) => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
});
}
//等待态,既没有成功,也没有失败
if(this.status == 'pending'){
//存放成功的回调
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => {
let x = onFulFilled(this.value);
resolvePromise(promise2, x, resolve, reject)
});
// 存放失败的回调
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
});
})
}
return promise2; // 调用then后返回一个新的promise
}
}
module.exports=P
复制代码
我们知道我们的回调函数都是异步执行,为了模拟异步,我们暂且用setTimeout模拟延迟执行
then(onFulFilled, onRejected) {
// 解决onFulFilled,onRejected没有传的问题
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
let promise2;
if (this.status === 'resolved') {
promise2 = new Promise((resolve, reject) => {
// 成功的逻辑 失败的逻辑
setTimeout(() => {
try {
let x = onFulFilled(this.value);
// 看x是不是promise 如果是promise 取他的结果 作为promise2,成功的结果
// 如果要是返回一个普通值 作为promise2,成功的结果
// resolvePromise可以解析x和promise2之间的关系
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
}, 0);
});
}
// 当前既没有完成 也没有失败
if (this.status === 'pending') {
// 存放成功的回调
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulFilled(this.value);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
}, 0)
});
// 存放失败的回调
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
})
}
return promise2; // 调用then后返回一个新的promise
}
复制代码
测一下我们的代码
let P=require('./myPromise')
let promise=new P((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
return new Promise((resolve,reject)=>{
resolve("2")
})
},err=>{
console.log("err1:",err)
}).then(data=>{
console.log("data2:",data) //2
return new Promise((resolve,reject)=>{
//resolve("3")
reject('failed')
})
},err=>{
console.log("err2:",err)
})
.then(data=>{
console.log("data3:",data) //undefined
return new Promise((resolve,reject)=>{
resolve("4")
})
},err=>{
console.log("err3:",err)
}).then(data=>{
console.log("data4:",data) //4
return new Promise((resolve,reject)=>{
resolve("5")
})
},err=>{
console.log("err4:",err)
}).then(data=>{
console.log("data5:",data)
}).catch(e=>{
console.log('e: ', e);
})
打印结果:
data1: 1
data2: 2
err3: failed
data4: undefined
data5: 5
复制代码
看下面这段:
let promise=new Promise((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
},err=>{
return '11'
console.log("err1:",err)
}).then(data=>{
console.log("data2:",data)
return new Promise((resolve,reject)=>{
//resolve("3")
reject('failed')
})
},err=>{
console.log("err2:",err)
})
执行结果:
data1: 1
data2: undefined
复制代码
let promise=new Promise((resolve,reject)=>{
resolve('1')
})
promise.then(data=>{
console.log("data1:",data) //1
return '11'
},err=>{
console.log("err1:",err)
}).then(data=>{
console.log("data2:",data)
return new Promise((resolve,reject)=>{
//resolve("3")
reject('failed')
})
},err=>{
console.log("err2:",err)
})
执行结果:
data1: 1
data2: 11
复制代码
我们看到:当我们的 then 返回的是普通值的时候,如:11,那么下一个 then 会在成功的回调中接收它的值 区别是:如果 上一个 then 在成功回调中返回,那么下一个就能接收的他的值,如果在失败的回调中返回,那么接收的就是 undefined 了。
如果返回的是新的 promise对象,那么下一个 then 就会根据成功回调和失败回调来执行响应的代码了。
好了,到此为止,就把 promise 的链式调用写完了,接下来看看 promise 的几个方法:
Promise.resolve()、Promise.reject()、Promise.race()、Promise.all()。 Promise.resolve()和Promise.reject() 都返回确定了状态的promise对象,之后可调用then、catch方法,可以创建一个确定状态的promise对象,用于将一些非promise 对象转换为promise 。
P.resolve=function(val){
return new Promise((resolve,reject)=>{
resolve(val)
})
}
P.reject=function(val){
return new Promise((resolve,reject)=>{
reject(val)
})
}
P.race=function(promises){
//promises 数组
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
}
})
}
P.all=function(promises){
return new Promise((resolve,reject)=>{
let arr=[];
let i=0; //为了保证获取全部成功,来设置索引
function processData(index,data){
arr[index]=data;
i++;
if(i===promises.length){
resolve(arr)
}
}
for(let i=0;i<promises.length;i++){
//成功的话才放入数组
promises[i].then(data=>{
processData(i,data);
}, reject);
}
})
}
复制代码
deferred对象
jQuery的deferred对象详解 http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html
Promise.deferred = Promise.defer = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
复制代码
3、生成器Generators/ yield
Generators是es6的新特性,可以帮助我们更好的控制一个函数的执行,他有两个特征:
- function关键字与函数名之间有一个星号;
- 函数体内部使用yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。
如果在函数执行到某的地方的时候,你想要看它的返回值,那么,只要用yield, 就会把yield后面的值返回,这个时候,函数是暂停的,你执行next()方法,可以看到返回的内容,返回的内容是个对象,有两个属性:value和done,value就是返回值,done是布尔值,false表示后面还有yield,true表示已经没有yield 了,每next()一次,它才走一步,直到走完,还是看例子吧:
function* g() {
yield '1';
yield '2';
}
var a = g();
a.next()
{value: "1", done: false}
a.next()
{value: "2", done: false}
a.next()
{value: undefined, done: true}
a.next()
{value: undefined, done: true}
复制代码
也可以用return返回最后一个值
function* g() {
yield '1';
yield '2';
return 'end';
}
var a = g();
复制代码
执行并打印结果:
a.next()
{value: "1", done: false}
a.next()
{value: "2", done: false}
a.next()
{value: "end", done: true}
a.next()
{value: undefined, done: true}
复制代码
4、co
co.js 是 TJ 大神基于es6的 generator 编写的 JavaScript 异步解决方案的库,用于让异步的代码 "同步化",相当于generator函数的一个自动执行器。
co.js 将一个异步函数thunk化
原函数
fs.readFile(path,callback)
thunk化后函数
let readFile = (path) => (callback) => fs.readFile(path, callback)
复制代码
var co = require('co');
function* g() {
var a=yield Promise.resolve(()=>{console.log(1)})
var b=yield Promise.resolve(2);
console.log(a)
console.log(b)
}
var a = g();
//a.next()
co(a);
执行:
[Function]
2
复制代码
需要注意的是,yield 后面必须是 promise, generator, array, or object 若想了解 co库 原理,请阅读阮一峰老师的:
http://www.ruanyifeng.com/blog/2015/05/co.html
4、async await
ES7 提出的async 函数是 Generator 函数的语法糖,想较于 Generator,Async 函数的改进在于下面四点:
内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
更好的语义。async 和 await 相较于 * 和 yield 更加语义化
更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)
返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用
复制代码
使用:
async 函数返回一个 Promise 对象
async 函数内部 return 返回的值。会成为 then 方法回调函数的参数。
async function f() {
return 'hello world'
};
f().then( (v) => console.log(v))
打印结果:
hello world
复制代码
如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。
async function e(){
throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));
打印:
Error: error
复制代码
async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变
也就是说,只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return 'done';
}
f().then(v => console.log(v));
打印结果:
等待6s后才输出 'done'
复制代码
正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise 如下面这个例子:
async function f() {
return await 1
};
f().then( (v) => console.log(v))
打印结果:
1
复制代码
如果返回的是 reject 的状态,则会被 catch 方法捕获。
学海无涯苦作舟,以此共勉!
参考: http://schifred.iteye.com/blog/2317978 https://www.cnblogs.com/starthust/p/3876458.html https://www.cnblogs.com/ranjianxi/p/6808147.html http://www.ruanyifeng.com/blog/2015/05/co.html https://segmentfault.com/a/1190000010244279