学习Promise笔记
栏目: JavaScript · 发布时间: 5年前
内容简介:MDN对Promise的定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。JavaScript的执行环境是单线程。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会阻塞其他任务。这个任务可称为主线程。但实际上还有其他线程,如事件触发线程,Ajax请求线程等。
什么是Promise?
MDN对Promise的定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。
在学习Promise之前得先了解同步与异步:
JavaScript的执行环境是单线程。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会阻塞其他任务。这个任务可称为主线程。
但实际上还有其他线程,如事件触发线程,Ajax请求线程等。
同步:
同步模式,即上述所说的单线程模式,一次只能执行一个任务,函数调用后需要等到函数执行结束,返回执行结果,才能进行下一个任务。如果这个任务执行的时间较长,就会导致线程阻塞。
var x = true; while(x); console.log("don't carry out"); // 不会执行
上面代码中的while是一个死循环,它会阻塞进程,因此第三句console不会执行。
异步:
异步模式,即与同步模式相反,可以一起执行多个任务,函数调用后不会立即执行返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后继续回调。
最常见的异步模式就是 定时器
的使用:
setTimeout(function() { console.log('taskA, asynchronous'); }, 0); console.log('taskB, synchronize'); //while(true); -------ouput------- taskB, synchronize taskA, asynchronous
虽然定时器延时的时间为0,但 taskA
还是晚于 taskB
执行。这是因为定时器是异步的,异步任务会再当前脚本的所有同步任务执行完后才会执行。如果同步代码中含有死循环,则这个异步任务不会执行,因为同步任务阻塞了进程。
回调函数:
上例中, setTimeout
里的 function
便是回调函数。可以理解为:(执行完)回调的函数。
WikiPedia
对 callback
的定义可以理解为:回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在其合适的时间执行这段(回调函数)的代码。回调函数不仅是可以用于异步调用,一般同步的场景也可以用回调。在同步调用下,可能一段时间后执行执行或不执行(未达到执行的条件)
/******************同步回调******************/ var fun1 = function(callback) { //do something console.log("before callback"); (callback && typeof(callback) === 'function') && callback(); console.log("after callback"); } var fun2 = function(param) { //do something var start = new Date(); while((new Date() - start) < 3000) { //delay 3s } console.log("I'm callback"); } fun1(fun2); -------output-------- before callback //after 3s I’m callback after callback
由于是同步调用,会阻塞后面的代码,如果 fun2
是个死循环,后面的代码就不执行了。
除了上面 setTimeout
为常见的异步回调,另外常见的异步即 Ajax
请求:
/******************异步回调******************/ function request(url, param, successFun, errorFun) { $.ajax({ type: 'GET', url: url, param: param, async: true, //默认为true,即异步请求;false为同步请求 success: successFun, error: errorFun }); } request('test.html', '', function(data) { //请求成功后的回调函数,通常是对请求回来的数据进行处理 console.log('请求成功啦, 这是返回的数据:', data); },function(error) { console.log('sorry, 请求失败了, 这是失败信息:', error); });
为什么使用Promise
利用 Promise
改写上面 Ajax
的例子:
function sendRequest(url, param) { return new Promise(function (resolve, reject) { request(url, param, resolve, reject); }); } sendRequest('test.html', '').then(function(data) { //异步操作成功后的回调 console.log('请求成功啦, 这是返回的数据:', data); }, function(error) { //异步操作失败后的回调 console.log('sorry, 请求失败了, 这是失败信息:', error); });
Promise
的优势在于它的重链式调用,可以避免层层嵌套回调。如果第一次 Ajax
请求后,还可以用它的返回的结果再次请求
request('test1.html', '', function(data1) { console.log('第一次请求成功, 这是返回的数据:', data1); request('test2.html', data1, function (data2) { console.log('第二次请求成功, 这是返回的数据:', data2); request('test3.html', data2, function (data3) { console.log('第三次请求成功, 这是返回的数据:', data3); //request... 继续请求 }, function(error3) { console.log('第三次请求失败, 这是失败信息:', error3); }); }, function(error2) { console.log('第二次请求失败, 这是失败信息:', error2); }); }, function(error1) { console.log('第一次请求失败, 这是失败信息:', error1); });
以上出现了多层调用,难以明白层级之间的关系,这就是常说的回调地狱( Pyramid of Doom
),而使用Promise,可以利用then进行链式调用,将异步操作以同步操作的流程表示出来。
sendRequest('test1.html', '').then(function(data1) { console.log('第一次请求成功, 这是返回的数据:', data1); }).then(function(data2) { console.log('第二次请求成功, 这是返回的数据:', data2); }).then(function(data3) { console.log('第三次请求成功, 这是返回的数据:', data3); }).catch(function(error) { //用catch捕捉前面的错误 console.log('sorry, 请求失败了, 这是失败信息:', error); });
Promise的基本用法
Promise
对象代表一个未完成、但预计将来会完成的操作。它有以下三种状态:
- pending:初始值,不是
fulfilled
,也不是rejected
- fulfilled:代表操作成功
- rejected:代表操作失败
Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就会一直保持这个状态。当状态发生变化,Promise.then绑定的函数就会被调用。
注意: Promise
一旦新建就会立即执行,无法取消。这也是它的缺点之一。
下面通过一个例子进一步讲解:
//构建Promise var promise = new Promise(function (resolve, reject) { if (/* 异步操作成功 */) { resolve(data); } else { /* 异步操作失败 */ reject(error); } });
类似构建对象,我们使用 new
来构建一个 Promise
。 Promise
接受一个函数作为参数,该函数的两个参数分别是 resolve
和 reject
。这两个函数就是回调函数,由JavaScript引擎提供。
-
resolve
函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参会素传递出去; -
reject
函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。Promise
实例生成以后,可以用then
方法指定resolve
和reject
状态的回调函数。
promise.then(onFulfilled, onRejected); promise.then(function(data) { // do something when success }, function(error) { // do something when failure });
then
方法会返回一个 Promise
。它有两个参数,分别为 Promise
从 pending
变为 fulfilled
和 rejected
时的回调函数(第二个参数非必选)。这两个函数都接受 Promise
对象传出的值作为参数。
简单来说, then
就是定义 resolve
和 reject
函数的,其 resolve
参数相当于:
function resolveFun(data) { //data为promise传出的值 }
而新建的 Promise
中的 ‘resolve(data)’
,则相当于执行 resolveFun
函数。
Promise
新建后就会立即执行。而 then
方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:
var promise = new Promise(function(resolve, reject) { console.log('before resolved'); resolve(); console.log('after resolved'); }); promise.then(function() { console.log('resolved'); }); console.log('outer'); -------output------- before resolved after resolved outer resolved
由于 resolve
指定的是异步操作成功后的回调函数,它需要等所有的同步代码执行后才会执行,因此最后打印 ‘resolved’
基本API
.then()
语法 :Promise.prototype.then(onFulfilled,onRejected)
对 Promise
添加 onFulfilled
和 onRejected
回调,并返回的是一个新的 Promise
实例(不是原来那个 Promise
实例),且返回值将作为参数传入这个新 Promise
的 resolve
函数。
因此可以使用链式写法。由于前一个回调函数,返回的还是一个 Promise
对象(即有异步操作),这时后一个回调函数,就会等待该 Promise
对象的状态发生变化,才会被调用。
.catch()
语法 :Promise.prototype.catch(onRejected)
该方法是.then(undefined,onRejected)的别名,用于指定发生错误时的回调函数。
promise.then(function(data) { console.log('success'); }).catch(function(error) { console.log('error', error); }); /*******等同于*******/ promise.then(function(data) { console.log('success'); }).then(undefined, function(error) { console.log('error', error); });
var promise = new Promise(function (resolve, reject) { throw new Error('test'); }); /*******等同于*******/ var promise = new Promise(function (resolve, reject) { reject(new Error('test')); }); //用catch捕获 promise.catch(function (error) { console.log(error); }); -------output------- Error: test
从上例可知, reject
方法的作用等同于抛错。
promise
对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个 catch
所捕获。 then
方法指定的回调函数,若抛出错误,也会被下一个 catch
捕获。 catch
中也能抛错,则需要后面的 catch
来捕获。
sendRequest('test.html').then(function(data1) { //do something }).then(function (data2) { //do something }).catch(function (error) { //处理前面三个Promise产生的错误 });
上面提到的, promise
状态一旦改变就会凝固,不会再改变。因此 promise
一旦 fulfilled
了,再抛错,也不会变为 rejected
,就不会被 catch
了
var promise = new Promise(function(resolve, reject) { resolve(); throw 'error'; }); promise.catch(function(e) { console.log(e); //This is never called });
如果没有使用 catch
方法指定处理错误的回调函数, Promise
对象抛出的错误不会传递到外层代码,即不会有任何反应( Chrome
会抛错),这是 Promise
的另一个缺点。
var promise = new Promise(function (resolve, reject) { resolve(x); }); promise.then(function (data) { console.log(data); });
如图所示,只有 Chrome
会抛错,且 promise
状态变为 rejected
, Firefox
和 Safari
中错误不会被捕获,也不会传递到外层代码,最后没有任何输出, promise
状态也变为 rejected
。
.all()
语法 :Promise.all(iterable)
该方法用于将多个 Promise
实例,包装成一个新的 Promise
实例。
var p = Promise.all([p1, p2, p3]);
Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象( p1
, p2
, p3
)均为 promise
实例(如果不是一个 promise
,该项会被用Promise.resolve转换为一个 promise
)。它的状态由这三个promise实例来决定。
- 当
p1
,p2
,p3
状态都为fulfilled
,p
的状态才会变为fulfilled
,并将三个promise
返回的结果,按参数的顺序(而不是resolved
的顺序)存入数组,传给p
的回调函数;
/* 例3.8 */ var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 3000, "first"); }); var p2 = new Promise(function (resolve, reject) { resolve('second'); }); var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(function(values) { console.log(values); }); -------output------- //约 3s 后 ["first", "second", "third"]
- 当
p1
,p2
,p3
其中之一状态变为rejected
,p
的状态也会变为rejected
,并把第一个被rejected
的promise
的返回值,传给p
的回调函数;
var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "one"); }); var p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "two"); }); var p3 = new Promise((resolve, reject) => { reject("three"); }); Promise.all([p1, p2, p3]).then(function (value) { console.log('resolve', value); }, function (error) { console.log('reject', error); // => reject three }); -------output------- reject three
- 这多个
promise
是同时开始、并行执行的,而不是顺序执行的。
function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } var startDate = Date.now(); Promise.all([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (values) { console.log(Date.now() - startDate + 'ms'); console.log(values); }); -------output------- 133ms //不一定,但大于128ms [1,32,64,128]
.race()
语法 :Promise.race(iterable)
该方法同样接受一个数组(或具有 Iterator
接口)作参数。当 p1
, p2
, p3
中有一个实例的状态发生改变(变为 fulfilled
或 rejected
), p
的状态就跟着改变。并把第一个改变状态的 promise
的返回值,传给 p
的回调函数。
var p1 = new Promise(function(resolve, reject) { setTimeout(reject, 500, "one"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then(function(value) { console.log('resolve', value); }, function(error) { //not called console.log('reject', error); }); -------output------- resolve two var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "three"); }); var p4 = new Promise(function(resolve, reject) { setTimeout(reject, 100, "four"); }); Promise.race([p3, p4]).then(function(value) { //not called console.log('resolve', value); }, function(error) { console.log('reject', error); }); -------output------- reject four
在第一个promise对象变为resolve后,并不会取消其他promise对象的执行,如下 var fastPromise = new Promise(function (resolve) { setTimeout(function () { console.log('fastPromise'); resolve('resolve fastPromise'); }, 100); }); var slowPromise = new Promise(function (resolve) { setTimeout(function () { console.log('slowPromise'); resolve('resolve slowPromise'); }, 1000); }); // 第一个promise变为resolve后程序停止 Promise.race([fastPromise, slowPromise]).then(function (value) { console.log(value); // => resolve fastPromise }); -------output------- fastPromise resolve fastPromise slowPromise //仍会执行
.resolve()
语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
它可以看做new Promise()的快捷方式
Promise.resolve('Success'); /*******等同于*******/ new Promise(function (resolve) { resolve('Success'); });
这段代码会让这个 Promise
对象立即进入 resolved
状态,并将结果 success
传递给 then
指定的 onFulfilled
回调函数。由于Promise.resolve()也是返回 Promise
对象,因此可以用.then()处理其返回值。
Promise.resolve('success').then(function (value) { console.log(value); }); -------output------- success
//Resolving an array Promise.resolve([1,2,3]).then(function(value) { console.log(value[0]); // => 1 }); //Resolving a Promise var p1 = Promise.resolve('this is p1'); var p2 = Promise.resolve(p1); p2.then(function (value) { console.log(value); // => this is p1 });
Promise.resolve()的另一个作用就是将 thenable
对象(即带有then的对象)转换为 promise
对象。
var p1 = Promise.resolve({ then: function (resolve, reject) { resolve("this is an thenable object!"); } }); console.log(p1 instanceof Promise); // => true p1.then(function(value) { console.log(value); // => this is an thenable object! }, function(e) { //not called });
下面两个例子,无论是在什么时候抛异常,只要 promise
状态变成 resolved
或 rejected
,状态不会再改变,这和新建 promise
是一样的。
//在回调函数前抛异常 var p1 = { then: function(resolve) { throw new Error("error"); resolve("Resolved"); } }; var p2 = Promise.resolve(p1); p2.then(function(value) { //not called }, function(error) { console.log(error); // => Error: error }); //在回调函数后抛异常 var p3 = { then: function(resolve) { resolve("Resolved"); throw new Error("error"); } }; var p4 = Promise.resolve(p3); p4.then(function(value) { console.log(value); // => Resolved }, function(error) { //not called });
.reject()
语法 :Promise.reject(reason)
这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。
Promise.reject(new Error('error')); /*******等同于*******/ new Promise(function (resolve, reject) { reject(new Error('error')); });
这段代码会让这个 Promise
对象立即进入 rejected
状态,并将错误对象传递给 then
指定的 onRejected
回调函数。
Promise常见问题
总结一下创建promise的流程:
- 使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一个promise对象
- 在fn中指定异步的处理
resolve reject
如果使用 ES6
的箭头函数,将会使写法更加简单清晰
接下来用例子说明 promise
使用过程中的注意点及容易犯的错误。
情景1:reject和catch的区别
- promise.then(onFulfilled,onRejected) 在
onFulfilled
中发生异常的话,在onRejected
中是捕获不到这个异常的。 - promise.then(onFilfilled).catch(onRejected) .then中产生的异常能在.catch中捕获。
一般情况,使用第二种,第二种的.catch()也可以使用.then()表示,它们本质上没有区别,.catch===.then(null,onRejected)
情景2:
如果在then中抛错,而没有对错进行处理(即 catch
),那么会一直保持 reject
状态,直到 catch
了错误。
function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
从代码的输出结果及流程,可以看出, A
抛错时,会按照taskA->onRejected->finalTask这个流程来处理。 A
抛错后,若没有对它进行处理,状态就会维持 rejected
, taskB
不会执行,直到 catch
了错误。
function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejectedA(error) { console.log("Catch Error: A", error); } function onRejectedB(error) { console.log("Catch Error: B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .catch(onRejectedA) .then(taskB) .catch(onRejectedB) .then(finalTask); -------output------- Catch Error: A ReferenceError: x is not defined Task B Final Task
在 TaskA
后多了对 A
的处理,因此, A
抛错时,会按照taskA->onRejectedA->taskB->finalTask这个流程来处理,此时 taskB
是正常执行的。
情景3:
每次调用 then
都会返回一个新创建的 promise
对象,而 then
内部只是返回了数据。
//方法1:对同一个promise对象同时调用 then 方法 var p1 = new Promise(function (resolve) { resolve(100); }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { console.log("finally: " + value); }); -------output------- finally: 100 //方法2:对 then 进行 promise chain 方式进行调用 var p2 = new Promise(function (resolve) { resolve(100); }); p2.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("finally: " + value); }); -------output------- finally: 400
第一种方法中, then
的调用几乎是同时开始执行的,且传给每个 then
的 value
都是100,这种方法应当避免。方法二才是正确的链式调用。
容易出现下面的错误写法:
function badAsyncCall(data) { var promise = Promise.resolve(data); promise.then(function(value) { //do something return value + 1; }); return promise; } badAsyncCall(10).then(function(value) { console.log(value); //想要得到11,实际输出10 }); -------output------- 10
正确的写法:
function goodAsyncCall(data) { var promise = Promise.resolve(data); return promise.then(function(value) { //do something return value + 1; }); } goodAsyncCall(10).then(function(value) { console.log(value); }); -------output------- 11
情景4:在异步回调中抛错,不会被catch到
// Errors thrown inside asynchronous functions will act like uncaught errors var promise = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); promise.catch(function(e) { console.log(e); //This is never called });
情景5:
promise
状态变为 resolve
或 reject
,就凝固了,不会再改变
console.log(1); new Promise(function (resolve, reject){ reject(); setTimeout(function (){ resolve(); //not called }, 0); }).then(function(){ console.log(2); }, function(){ console.log(3); }); console.log(4); -------output------- 1 4 3
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- Golang学习笔记-调度器学习
- Vue学习笔记(二)------axios学习
- 算法/NLP/深度学习/机器学习面试笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning Vue.js 2
Olga Filipova / Packt Publishing / 2017-1-5 / USD 41.99
About This Book Learn how to propagate DOM changes across the website without writing extensive jQuery callbacks code.Learn how to achieve reactivity and easily compose views with Vue.js and unders......一起来看看 《Learning Vue.js 2》 这本书的介绍吧!