JavaScript 编写异步代码
回调函数
事件监听
发布订阅,消息中心
Promise/A+
Generator
async/await
Thunk
Promise 示例 Promise 值具有穿透性,穿透一个没有用的 .then() 直接到达 一个有用的 .then()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 let p1 = new Promise ((resolve,reject ) => { resolve(1 ) }) p1.then() .then(value => { console .log(value); return value + 1 }).then(value => { console .log(value); }) let p2 = Promise .resolve(1 );p2.then(v => { v + 1 }) .then(v => { console .log(v)}); let p3 = Promise .resolve(1 );p3.then(v => v + 1 ); p3.then(v => console .log(v)); let p4 = Promise .resolve(1 );p4.then(v => { return new Promise ((resolve,reject ) => { setTimeout (() => resolve(2 )) }) }).then(v => console .log(v)); let p1 = new Promise ((resolve , reject ) => { reject(new Error ('some wrong' )) }) let p2 = p1.then( value => { console .log(value) }, reason => { console .log(reason.message) } ) p2.then(()=> { throw new Error ('something wrong 1' ) }).catch(reason => { console .log(reason.message) }) Promise .reject(new Error ('error' ))) .catch(reason => { console .log(reason.message); return `${reason.message} occured` }).then(v => console .log(v)); function sleep (duration ) { return new Promise ((resolve,reject ) => { setTimeout (() => {resolve(),duration}) }) } async function run ( ) { const start = Date .now(); await sleep(1000 ); await sleep(2000 ); await sleep(3000 ); console .log(Date .now() - start) } run() function run1 ( ) { const start = Date .now(); return Promise .resolve() .then(function ( ) { return sleep(1000 )}) .then(function ( ) { return sleep(2000 )}) .then(function ( ) { return sleep(3000 )}) .then(function ( ) { console .log(Date .now() - start)}) } run1();
实现 Promise 需要注意
符合 Promise/A+ 标准
不暴露过多的函数和变量
Promise 状态只能修改一次
then() 和 catch 需要异步执行
then 能够实现链式调用
then 返回新的 Promise
then 值能够穿透
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 const PENDING = 0 ;const FULFILLED = 1 ;const REJECTED = 2 ;class Promise { constructor (fn ) { this ._state = PENDING; this ._data = null ; this ._onFulfilledCallbacks = []; this ._onRejectedCallbakcs = []; run(this ,fn); } static deferred (){ const dfd = {} dfd.promise = new promise ((resonlve,reject ) => { dfd.resolve = resolve; dfd.reject = reject; }) return dfd; } then (onFulfilled, onRejected ) { if (typeof onFulfilled !== 'function' ){ onFulfilled = function (data ) { return data } } if (typeof onRejected !== 'function' ){ onRejected = function (reason ) { throw reason } } let promise2; if (this ._state === FULFILLED){ promise2 = new Promise ((resolve,reject ) => { setTimeout (()=> { try { const x = onFulfilled(this ._data) resolvePromise(promise2,x,resolve,reject) }catch (e){ reject(e); } }) }) }else if (this ._state === REJECTED){ promise2 = new Promise ((resolve,reject ) => { setTimeout (()=> { try { const x = onRejected(this ._data) resolvePromise(promise2,x,resolve,reject) }catch (e){ reject(e); } }) }) }else if (this ._state === PENDING){ promise2 = new Promise ((resolve,reject )=> { this ._onFulfilledCallbacks.push(data => { try { const x = onFulfilled(data) resolvePromise(promise2,x,resolve,reject) }catch (e){ reject(e) } }) this ._onRejectedCallbakcs.push(reason => { try { const x = onRejected (reason) resolvePromise(promise2,x,resolve,reject) }catch (e){ reject(e) } }) }) } } return promise2 } catch (onRejected){ return this .then(undefined ,onRejected) } function resolvePromise (promise2,x,resolve,reject ) { if (x instanceof Promise ){ return x.then(resolve,reject); }else { resolve(x) } } function run (promise,fn ) { try { fn( data => { resolve(promise,data) }, reason => { reject(promise,reason) } ) }catch (e){ reject(promise,e) } } function resolve (promise,data ) { if (data instanceof Promise ){ return data.then( d => { resolve (promise,d)}, r => { reject (promise,r)} ) } if (promise._state !== PENDING){ callback(data) } setTimeout (()=> { promise._state = FULFILLED; promise._data = data; for (let callback of promise._onFulfilledCallbacks) { callback(data) } }) } function reject (promise,reason ) { if (promise._state !== PENDING){ return } setTimeout (()=> { promise_state = REJECTED promise_data = reason for (let callback of promise__onRejectedCallbakcs){ callback(reason ) } }) } module .exports = Promise ;
校验 是否符合 promise 规范
promises-aplus-tests
Promise 是什么 按照用途来解释
主要用于异步计算。
可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
可以在对象之间传递和操作 Promise,帮助我们处理队列。
为什么会有 Promise JavaScript 包含大量异步操作
JavaScript 为检查表单而生。 创造它的首要目标是操作 DOM。 所以,JavaScript 的操作大多是异步的。 是 UI 语言造成界面冻结是大忌
为什么异步操作可以避免界面冻结 同步问题:按顺序执行,按顺序完成。
异步:完成顺序和执行顺序无关。
异步操作的常见语法 事件监听与响应:
1 2 3 4 5 6 7 8 document .getElementById("start" ).addEventListener("click" , start, false ); function start ( ) { } $("#start" ).on("click" , start);
异步操作的常见语法
1 2 3 4 5 6 7 8 9 10 $.ajax("http://baidu.com" , { success: function (res ) { }, }); $(function ( ) { });
浏览器中的 JavaScript
异步操作以事件为主
回调主要出现在 Ajax 和 File API
Node.js 的出现 对异步的依赖进一步加剧了
无阻赛高并发,是 Node.js 的招牌。
异步操作是其保障。
大量操作依赖回调函数。
异步回调的问题
嵌套层次很深,难以维护
无法正常使用 return 和 throw
无法正常检索堆栈信息
多个回调之间难以建立联系
Promise 详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 new Promise ( function (resolve, reject ) { resolve(); reject(); } ).then( function A ( ) { }, function B ( ) { } );
Promise 是一个代理对象,它和原先要进行的操作并无关系。 它通过引入一个回调,避免更多的回调。
Promise 有 3 个状态 pending [待定] 初始状态 fulfilled [实现] 操作成功 reject [被否定] 操作失败
Promise 状态发生改变,就会触发 .then() 里的响应函数处理后续步骤。 Promise 状态一经改变,不会再变。
范例
1 2 3 4 5 6 7 new Promise ((resolve ) => { setTimeout (() => { resolve("hello" ); }, 2000 ); }).then((value ) => { console .log(value + "world" ); });
两步执行范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 new Promise ( resolve => { setTimeout (()=> { resolve('hello' ); },2000 ); }).then( value => { console .log(value); return new Promise ( resolve => { setTimeout (() => { resolve('world' ); },2000 ); }); }).then( value => { console .log( value + 'world' ); })
对已完成的 promise 执行 1 2 3 4 5 6 7 8 9 10 11 12 13 let promise = new Promise (resolve => { setTimeout (() => { console .log('the promise fulfilled' ); resolve('hello,world' ); },1000 ) }) setTimeout (()=> { promise.then( value => { console .log(value); }) },3000 )
then 里不返回 promise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 new Promise ( resolve => { setTimeout (() => { resolve('hello' ); },2000 ); }).then(value => { console .log(value); console .log('everyone' ); (function ( ) { return new Promise (resolve => { setTimeout (()=> { console .log('Mr.Laurence' ); resolve(' Merry Xmas ' ); },2000 ) }); }()); return false ; }).then( value => { console .log(value + ' world' ); })
.then()
.then() 接受两个函数作为参数,分别代表 fulfilled 和 rejected
.then() 返回一个新的 Promise 实例,所以它可以链式调用
当前面的 Promise 状态改变时,.then() 根据其最终状态,选择特定的状态响应函数执行
状态响应函数可以返回新的 Promise,或其他值
如果返回新的 Promise,那么下一级 .then() 会在新 Promise 状态改变之后执行。
如果返回其他任何值,则会立刻执行下一级 .then()
.then()里有 .then()的情况
因为 .then() 返回的还是 Promise 实例。
会等里面的 .then() 执行完,再执行外面的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 console .log('start' )new Promise ( resolve => { console .log('Step 1' ); setTimeout (() => { resolve('1000' ); },1000 ); }).then(value => { return new Promise ( resolve => { console .log('Step 1-1' ); setTimeout (()=> { resolve(110 ); },1000 ) }).then( value => { console .log('Step 1-2' ); return value; }).then(value => { console .log('Step 1-3' ); return value; }) }).then( value => { console .log(value); console .log('Step 2' ); })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 console .log('start' )new Promise ( resolve => { console .log('Step 1' ); setTimeout (() => { resolve('1000' ); },1000 ); }).then(value => { return new Promise ( resolve => { console .log('Step 1-1' ); setTimeout (()=> { resolve(110 ); },1000 ) }) }).then( value => { console .log('Step 1-2' ); return value; }).then(value => { console .log('Step 1-3' ); return value; }).then( value => { console .log(value); console .log('Step 2' ); })
下面 Promise 的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 doSomething().then(function ( ) { return doSomethingElse() }); doSomething().then(function ( ) { doSomethingElse() }); doSomething().then( doSomethingElse() ); doSomething().then( return doSomethingElse );
错误处理 Promise 会自动捕获内部异常,并交给 rejected 响应函数处理。 错误处理两种做法:
reject(‘错误信息’).then(null,message =>{})
throw new Error(‘错误信息’).catch( message => {})
推荐第二种,更加清晰好读,并且可以捕获前面的错误。
1 2 3 4 5 6 7 8 9 new Promise ( resolve => { setTimeout (()=> { throw new Error ('bye' ); },2000 ); }).then( value => { console .log(value + 'world' ); }).catch( error => { console .log( 'Error:' ,error.message ) })
1 2 3 4 5 6 7 8 9 new Promise ( (resolve,reject ) => { setTimeout (()=> { reject('bye' ); },2000 ); }).then( value => { console .log(value + 'world' ); },value => { console .log('Error' ,value); })
.catch() + .then() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 new Promise (resolve => { setTimeout (()=> { resolve(); },1000 ) }).then(()=> { console .log('start' ); throw new Error ('test error' ); }).catch( err => { console .log('I catch ' ,err); }).then(()=> { console .log('arrive here' ); }).then(()=> { console .log('...and here' ); }).catch( err => { console .log('No,I catch' ,err); })
注意:
强烈建议在所有队列最后都加上 .catch(),以避免漏掉错误处理造成意想不到的问题。
1 2 3 4 5 6 doSomething() .doAnotherThing() .doMoreThing() .catch( err => { console .log(err); })
Promise 常用函数 Promise.all() 批量执行
Promise.all([p1,p2,p3,…]) 用于将多个 Promise 实例,包装成一个新的 Promise 实例
返回的实例就是普通 Promise
接收一个数组作为参数。
数组里可以是 Promise 对象,也可以是别的值,只有 Promise 会等待状态改变。
当所有子 Promise 都完成,该 Promise 完成,返回值是全部值的数组
有任何一个失败,该 Promise 失败,返回值是第一个失败的子 Promise 的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Promise .all([1 ,2 ,3 ]).then( all => { console .log('1: ' ,all); return Promise .all([function ( ) { console .log('ooxx' ); },'xxoo' ,false ]); }).then(all => { console .log('2: ' ,all); let p1 = new Promise ( resolve => { setTimeout (()=> { resolve('I\'m P1' ); },1500 ) }) let p2 = new Promise ( resolve => { setTimeout (()=> { resolve('I\'m P2' ); },1450 ) }) return Promise .all([p1,p2]); }).then( all => { console .log('3: ' ,all); let p1 = new Promise ( resolve => { setTimeout (() => { resolve('I\'m P1' ); },1500 ) }) let p2 = new Promise ( (resolve,reject ) => { setTimeout (() => { reject('I\'m P2' ); },1000 ) }) let p3 = new Promise ( (resolve,reject ) => { setTimeout (() => { reject('I\'m P3' ); },3000 ) }) }).then( all => { console .log('all' ,all); }).catch(err => { console .log(err); })
Promise.add() 和 .map() 连用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const fs = require ('fs' );const path = require ('path' );const FileSystem = require ('./FileSystem' );function findLargest (dir ) { return FileSystem.readDir(dir, 'utf-8' ).then( files => { return Promise .all( files.map( file => { return new Promise (resolve => { fs.stat(path.join(dir, file), (err, stat ) => { if (err) throw err; if (stat.isDirectory()) { return resolve({ size: 0 }); } stat.file = file; resolve(stat); }); }); })); }).then( stats => { let biggest = stats.reduce( (memo, stat ) => { if (memo.size < stat.size) { return stat; } return memo; }); return biggest.file; }) }
实现队列 使用 .forEach()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function queue (things ) { let promise = Promise .resolve(); things.forEach( thing => { promise = promise.then(()=> { return new Promise ( resolve => { doThing(thing,()=> { resolve(); }); }) }) }) return promise; } queue('lots' ,'of' ,'things' ,...)
常见错误:
没有把 .then()产生的新 Promise 实例赋给 promise,没有生成队列。
使用 .reduce()
1 2 3 4 5 6 7 8 9 10 11 12 function queue (things ) { return things.reduce((promise,thing ) => { return promise.then(() => { return new Promise ( resolve => { doThing(thing,()=> { resolve(); }); }); }); },Promise .resolve()); } queue('lots' ,'of' ,'things' ,...)
常见错误:
Promise 实例创建之后,会立刻运行执行器代码,所以这个也无法达成队列的效果。
半成品爬虫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let url = ['http://blog.meathill.com/' ];function fetchAll (urls ) { return urls.reduce((promise, url ) => { return promise.then( () => { return fetch(url); }); }, Promise .resolve()); } function fetch (url ) { return spider.fetch(url) .then( content => { return saveOrOther(content); }) .then( content => { let links = spider.findLinks(content); return fetchAll(links); }); } fetchAll(url);
Promise.resolve() 返回一个 fulfilled 的 Promise 实例,或原始 Promise 实例。
参数为空,返回一个状态为 fulfilled 的 Promise 实例。
参数是一个跟 Promise 无关的值,同上,不过 fulfilled 响应函数会得到这个参数
参数为 Promise 实例,则返回该实例,不做任何修改
参数为 thenable,立刻执行它的 .then()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Promise .resolve().then( (value ) => { console .log('Step 1' , value); return Promise .resolve('Hello' ); }).then( value => { console .log(value,'World' ); return Promise .resolve(new Promise ( resolve => { setTimeout (()=> { resolve('Good' ); },2000 ); })); }).then( value => { console .log(value, 'evening' ); return Promise .resolve({ then ( ) { console .log(', everyone' ); } }) })
Promise.reject() 返回一个 rejected 的 Promise 实例。
Promise.reject() 不认 thenable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let promise = Promise .reject('something wrong' );promise.then( () => { console .log('it\'s ok' ); }).catch( () => { console .log('no, it\'s not ok' ); return Promise .reject({ then ( ) { console .log('it will be ok' ); }, catch () { console .log('not yet' ); } }); });
Promise.race() 类似 Promise.all(),区别在于它有任意一个完成就算完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let p1 = new Promise (resolve => { setTimeout (() => { resolve('I\'m P1' ); }, 10000 ); }); let p2 = new Promise (resolve => { setTimeout (() => { resolve('I\'m P2' ); }, 2000 ) }); Promise .race([p1, p2]).then(value => { console .log(value); });
常见用法:
把异步操作和定时器放在一起
如果定时器先触发,就认为超时,告知用户
实际开发的 Promise 异步回调包装成 Promise 优点:
可读性更好
返回的结果可以加入任何 Promise 队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const fs = require ('fs' );module .exports = { readDir: function (path, options ) { return new Promise ( resolve => { fs.readdir(path, options, (err, files ) => { if (err) { throw err; } resolve(files); }); }); }, readFile: function (path, options ) { return new Promise ( resolve => { fs.readFile(path, options, (err, content ) => { if (err) { throw err; } resolve(content); }); }); } }; const fs = require ('./FileSystem' );fs.readFile('../README.md' ,'utf-8' ).then(content => { console .log(content); })
把任意异步操作包装成 Promise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let confirm = popupManager.confirm('您确定么?' );confirm.promise.then(() => { }) .catch(() => { }); class Confirm { constructor ( ) { this .promise = new Promise ( (resolve, reject ) => { this .confirmButton.onClick = resolve; this .cancelButton.onClick = reject; }) } }
两步执行 1 2 3 4 5 6 7 8 9 10 11 12 13 let promise = new Promise (resolve => { setTimeout (() => { console .log('the promise fulfilled' ); resolve('hello,world' ); },1000 ) }) setTimeout (()=> { promise.then( value => { console .log(value); }) },3000 )
实际开发会对请求进行 promise 化,两种情况
返回的数据直接更改值
1 2 3 4 5 6 7 8 9 10 getlist (data ) { this .api.getlist(data).then(res => { this .list = res.data.data; }) } pullDown ( ) { this .getlist(data); }
返回数据传递给下个调用的函数
1 2 3 4 5 6 7 8 9 10 11 12 getlist (data ) { return this .api.getlist(data).then(res => { return Promise .resolve(res.data.data); }) } querySearchAsync ( ) { this .getlist(data).then((value )=> { this .searchlist = value; }) }
jQuery jQuery 已经实现了 Promise。1.5 版开始尝试, 3.0版完成的 Promise。
参考 jQuery 的 ajax 实现。
1 2 3 4 5 $.ajax(url,{ dataType: 'json' }).then(json => { })
IE 如果需要在 IE 中使用 Promise,有两个选择:
Fetch API Fetch API 是 XMLHttpRequest 的现代化替代方案。 可以在 service workers、Cache API 使用。
更强大,更友好。
直接返回一个 Promise 实例。
response 是一个二进制数据流,需要调用 json() 方法可以转换成 json 数据
1 2 3 4 5 6 7 fetch('some.json' ).then( res => { return res.json() }).then( json => { }).catch( err => { console .log(err); })
async/await ES2017 新增运算符,新的语言元素
赋予 JavaScript 以顺序手法编写异步脚本的能力
既保留异步运算的无阻塞特性,还继续使用同步写法。
还能正常使用 return/try/catch。
async/await 仍然需要 Promise!
希望全面了解,请参考 async/await 入门 。
1 2 3 4 async function f1 ( ) { const result = await resolveAfter2Seconds(); console .log(result); }
协程
协程A开始执行。
协程A执行到一半,进入暂停,执行权转移到协程B。
(一段时间后)协程B交还执行权。
协程A恢复执行。
Thunk 参数求值
传名调用,在执行时求值 1 2 3 4 5 6 7 8 9 10 11 12 13 function f (m ) { return m * 2 ; } f(x + 5 ); var thunk = function ( ) { return x + 5 ; }; function f (thunk ) { return thunk() * 2 ; }
在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
1 2 3 4 5 6 7 8 9 10 11 12 fs.readFile(fileName, callback); var readFileThunk = Thunk(fileName);readFileThunk(callback); var Thunk = function (fileName ) { return function (callback ) { return fs.readFile(fileName, callback); }; };
任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式 。下面是一个简单的 Thunk 函数转换器。
1 2 3 4 5 6 7 8 9 10 11 12 13 var Thunk = function (fn ) { return function ( ) { var args = Array .prototype.slice.call(arguments ); return function (callback ) { args.push(callback); return fn.apply(this , args); } }; }; var readFileThunk = Thunk(fs.readFile);readFileThunk(fileA)(callback);
Thunkify 模块 生产环境的转换器,建议使用 Thunkify 模块
Thunk 函数的自动流程管理 控制执行权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 var fs = require ('fs' );var thunkify = require ('thunkify' );var readFile = thunkify(fs.readFile);var gen = function * ( ) { var r1 = yield readFile('/etc/fstab' ); console .log(r1.toString()); var r2 = yield readFile('/etc/shells' ); console .log(r2.toString()); }; var g = gen();var r1 = g.next();r1.value(function (err, data ) { if (err) throw err; var r2 = g.next(data); r2.value(function (err, data ) { if (err) throw err; g.next(data); }); }); function run (fn ) { var gen = fn(); function next (err, data ) { var result = gen.next(data); if (result.done) return ; result.value(next); } next(); } run(gen);
Generator Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function * gen (x ) { try { var y = yield x + 2 ; } catch (e){ console .log(e); } return y; } var g = gen(1 ); g.next() g.next() g.next(2 ) g.throw('出错了' )
相关资料Promise 入门 √ Generator 函数的含义与用法 √ Thunk 函数的含义和用法 √ fetch polyfill兼容库 彻底搞懂JS事件中的循环机制 Event Loop 巨大的提升!重启的async函数和promises“译”
async/await的基础用法
Promise 初入门径 Promise 探讨 jQuery的deferred对象详解 Promises/A+ 规范
相关代码
– 开源es6-promise