共计 5721 个字符,预计需要花费 15 分钟才能阅读完成。
原文链接:浅谈 NodeJS 之 Promise
NodeJS 里为解决同步执行的问题,常常须要嵌套调用,Promise 就是解决这一问题的,其实微信小程序开发也有同样的问题,也提供了 promise 形式的解决办法。
以下是援用的原文:
啥是 Promise?
Promise 是一个构造函数,办法有 all、reject、resolve 这几个,原型上有 then、catch 等办法。那么 new Promise 进去的对象必定就有 then、catch 办法。跟着我一步一步学习吧。本文能让你对 Promise 有一个颠覆的意识。
让咱们 new 一个吧:
var promise = new Promise(function(resolve, reject){
// 异步操作
setTimeout(function(){console.log('执行结束!');
resolve('xxx');
}, 2000);
});
执行异步操作,2 秒后输入“执行结束!”,并调用 resolve 办法。
Promise 的构造函数接管一个参数,是函数,并且传入两个参数:resolve,reject,别离示意异步操作执行胜利后的回调函数和异步操作执行失败后的回调函数。其实依照规范来讲,resolve 是将 Promise 的状态置为 fullfiled,reject 是将 Promise 的状态置为 rejected。
以上咱们 new 了一个对象,并没有调用它,传进去的函数就曾经执行了,所以咱们用 Promise 的时候个别是包在一个函数中,在须要的时候去运行这个函数,如:
function runAsync(){var promise = new Promise(function(resolve, reject){setTimeout(function(){console.log(' 执行结束);
resolve('xxx');
}, 2000);
});
return promise;
}
runAsync();
包装函数的作用和 resolve 的作用
执行这个函数咱们能失去了一个 Promise 对象,即包装好的函数最初,会 return 出 Promise 对象,。还记得 Promise 对象上有 then、catch 办法吧?这就是弱小之处了,看上面的代码:
runAsync().then(function(data){console.log(data);
// 这里用传过来的数据做其余操作
});
在 runAsync()的返回上间接调用 then 办法,then 接管一个参数,是函数,并且会拿到咱们在 runAsync 中调用 resolve 时传的的参数。运行这段代码,2 秒后输入“执行结束”,紧接着输入“xxx”。
then 外面的函数就跟咱们平时的回调函数一个意思,可能在 runAsync 这个异步工作执行实现之后被执行,这就是 Promise 的作用,艰深讲,就是把原来的回调写法分离出来,在异步操作执行完后,用链式调用的形式执行回调函数。
如果只有一次调用,也能够这么写,成果一样:
function runAsync(callback){setTimeout(function(){console.log('执行结束');
callback('xxx');
}, 2000);
}
runAsync(function(data){console.log(data);
});
那么有多层回调该怎么办?
Promise 的劣势在于,能够在 then 办法中持续写 Promise 对象并返回,而后持续调用 then 来进行回调操作。
链式操作的用法
所以,从外表上看,Promise 只是可能简化层层回调的写法,而本质上,Promise 的精华是“状态”,用保护状态、传递状态的形式来使得回调函数可能及时调用,它比传递 callback 函数要简略、灵便的多。所以应用 Promise 的正确场景是这样的:
function runAsync1(){var promise = new Promise(function(resolve, reject){setTimeout(function(){console.log('异步 1 实现');
resolve('xxx1');
}, 1000);
});
return promise ;
}
function runAsync2(){var promise = new Promise(function(resolve, reject){setTimeout(function(){console.log('异步 2 实现');
resolve('xxx2');
}, 2000);
});
return promise ;
}
function runAsync3(){var promise = new Promise(function(resolve, reject){setTimeout(function(){console.log('异步 3 实现');
resolve('xxx3');
}, 2000);
});
return promise ;
}
runAsync1()
.then(function(data){console.log(data);
return runAsync2();})
.then(function(data){console.log(data);
return runAsync3();})
.then(function(data){console.log(data);
});
这样可能按程序,每隔两秒输入每个异步回调中的内容,在 runAsync2 中传给 resolve 的数据,能在接下来的 then 办法中拿到。运行后果如下:
异步 1 实现
xxx1
异步 2 实现
xxx2
异步 3 实现
xxx3
在 then 办法中,你也能够间接 return 数据而不是 Promise 对象,在前面的 then 中就能够接管到数据了,比方咱们把下面的代码批改成这样:
runAsync1()
.then(function(data){console.log(data);
return runAsync2();})
.then(function(data){console.log(data);
return '间接返回数据'; // 这里间接返回数据
})
.then(function(data){console.log(data);
});
那么输入就变成了这样:
异步 1 实现
xxx1
异步 2 实现
xxx2
间接返回数据
reject 的用法
咱们对 Promise 有了最根本的理解。那么 ES6 的 Promise 还有哪些性能。咱们只用了 resolve,还没用 reject。咱们后面的例子只有“执行胜利”的回调,还没有“失败”的状况,reject 的作用就是把 Promise 的状态置为 rejected,这样咱们在 then 中就能捕捉到,而后执行“失败”状况的回调。不多说看代码。
function getNumber(){var promise = new Promise(function(resolve, reject){
// 异步操作
setTimeout(function(){var num = Math.ceil(Math.random()*10); // 生成 1 -10 的随机数
if(num<=5){resolve(num);
}
else{reject('数字大了');
}
}, 2000);
});
return promise;
}
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
},
function(reason, data){console.log('rejected');
console.log(reason);
}
);
getNumber 函数用来异步获取一个数字,2 秒后执行实现,如果数字小于等于 5,咱们认为是“胜利”了,调用 resolve 批改 Promise 的状态。否则认为是“失败”了,调用 reject 并传递一个参数,作为失败的起因。
运行 getNumber 并且在 then 中传了两个参数,then 办法能够承受两个参数,第一个对应 resolve 的回调,第二个对应 reject 的回调。所以咱们可能别离拿到他们传过来的数据。屡次运行这段代码,你会随机失去上面两种后果:
resolved
1
或者
rejected
数字大了
catch 的用法
Promise 对象除了 then 办法,还有一个 catch 办法,它和 then 的第二个参数一样,用来指定 reject 的回调,用法是这样:
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
})
.catch(function(reason){console.log('rejected');
console.log(reason);
});
成果和写在 then 的第二个参数外面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是下面 then 中的第一个参数)时,如果抛出异样了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 办法中。请看上面的代码:
getNumber()
.then(function(data){console.log('resolved');
console.log(data);
console.log(somedata); // 此处的 somedata 未定义
})
.catch(function(reason){console.log('rejected');
console.log(reason);
});
在 resolve 的回调中,咱们 console.log(somedata); 而 somedata 这个变量是没有被定义的。如果咱们不必 Promise,代码运行到这里就间接在控制台报错了,不往下运行了。然而在这里,会失去这样的后果:
resolved
4
rejected
ReferenceError: somedata is not defined(...)
也就是说进到 catch 办法外面去了,而且把谬误起因传到了 reason 参数中。即使是有谬误的代码也不会报错了,这与咱们的 try/catch 语句有雷同的性能。
all 的用法
Promise 的 all 办法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。咱们仍旧应用下面定义好的 runAsync1、runAsync2、runAsync3 这三个函数,看上面的例子:
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){console.log(results);
});
用 Promise.all 来执行,all 接管一个数组参数,外面的值最终都算返回 Promise 对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到 then 外面。那么,三个异步操作返回的数据哪里去了呢?都在 then 外面呢,all 会把所有异步操作的后果放进一个数组中传给 then,就是下面的 results。所以下面代码的输入后果就是:
异步 1 实现
异步 2 实现
异步 3 实现
[“xxx1”,”xxx2”,”xxx3”]
有了 all,你就能够并行执行多个异步操作,并且在一个回调中解决所有的返回数据。关上网页时,事后加载须要用到的各种资源如图片、flash 以及各种动态文件。所有的都加载完后,咱们再进行页面的初始化。
race 的用法
all 办法的成果实际上是“谁跑的慢,以谁为准执行回调”,那么绝对的就有另一个办法“谁跑的快,以谁为准执行回调”,这就是 race 办法,这个词原本就是赛跑的意思。race 的用法与 all 一样,咱们把下面 runAsync1 的延时改为 1 秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){console.log(results);
});
这三个异步操作同样是并行执行的。后果你应该能够猜到,1 秒后 runAsync1 曾经执行完了,此时 then 外面的就执行了。后果是这样的:
异步 1 实现
xxx1
异步 2 实现
异步 3 实现
在 then 外面的回调开始执行时,runAsync2()和 runAsync3()并没有进行,仍旧再执行。于是再过 1 秒后,输入了他们完结的标记。
咱们能够用 race 给某个异步申请设置超时工夫,并且在超时后执行相应的操作,代码如下:
// 申请某个图片资源
function requestImg(){var promise = new Promise(function(resolve, reject){var img = new Image();
img.onload = function(){resolve(img);
}
img.src = 'xxx';
});
return promise;
}
// 延时函数,用于给申请计时
function timeout(){var promise = new Promise(function(resolve, reject){setTimeout(function(){reject('图片申请超时');
}, 5000);
});
return promise;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){console.log(results);
})
.catch(function(reason){console.log(reason);
});
requestImg 函数会异步申请一张图片,我把地址写为 ”xxx”,所以必定是无奈胜利申请到的。timeout 函数是一个延时 5 秒的异步操作。咱们把这两个返回 Promise 对象的函数放进 race,于是他俩就会赛跑,如果 5 秒之内图片申请胜利了,那么便进入 then 办法,执行失常的流程。如果 5 秒钟图片还未胜利返回,那么 timeout 就跑赢了,则进入 catch,报出“图片申请超时”的信息。运行后果如下:
GET file:///D:/nodeJS/xxx net::ERR_FILE_NOT_FOUND
图片申请超时